From 93abc1af08067b4ada19cf802f4d475a6f748190 Mon Sep 17 00:00:00 2001 From: Steven Lindsay Date: Fri, 12 Dec 2025 13:27:39 +0000 Subject: [PATCH 01/33] docs: add guide for integrating external systems with Ably Chat --- .../guides/chat/external-integrations.mdx | 777 ++++++++++++++++++ 1 file changed, 777 insertions(+) create mode 100644 src/pages/docs/guides/chat/external-integrations.mdx diff --git a/src/pages/docs/guides/chat/external-integrations.mdx b/src/pages/docs/guides/chat/external-integrations.mdx new file mode 100644 index 0000000000..aa1d3196cb --- /dev/null +++ b/src/pages/docs/guides/chat/external-integrations.mdx @@ -0,0 +1,777 @@ +--- +title: "Guide: Integrate external systems with Ably Chat" +meta_description: "Extend Ably Chat with external integrations: process messages with AI, webhooks, and custom logic at scale." +meta_keywords: "chat integrations, external systems, webhooks, AI chat, message processing, chat automation, integration rules, message routing" +--- + +Ably Chat is designed for extensibility. Whether you need to process messages with AI, translate content in real-time, trigger business workflows, or enrich messages with external data, Ably's integration capabilities enable you to extend your chat application without compromising on scale, reliability, or performance. + +This guide explains how to architect integrations that process chat messages through external systems and respond back to the chat in real-time. Before diving into the technical implementation, it's important to understand your architectural goals and the role your external system will play. + +## Why integrate external systems? + +Integrating external systems with Ably Chat enables you to extend your chat application beyond simple message delivery. Common integration use cases include: + +* **AI-powered assistants:** Process commands or questions through language models and respond with helpful information. +* **Real-time translation:** Automatically translate messages for multilingual chat rooms. +* **Content enrichment:** Expand URLs into rich previews, detect and process mentions, or add contextual information. +* **Analytics and monitoring:** Analyze sentiment, track engagement metrics, or identify trending topics. +* **Workflow automation:** Trigger external business processes based on chat activity. +* **Command processing:** Handle slash commands that invoke server-side logic. + +Unlike [chat moderation](/docs/chat/moderation), which filters or blocks content before or after publication, integrations focus on processing messages and responding with new content or triggering external actions. Also unlike [exporting chat data](/docs/guides/chat/export-chat), which stores all messages for long-term retention, integrations process specific messages matched by integration rules. + +## Why Ably for external integrations? + +Ably Chat integrations are built on Ably's proven platform architecture, designed to handle integration workloads at any scale: + +* **Reliable message delivery:** Every integration benefits from Ably's [four pillars of dependability](/docs/platform/architecture): Performance, Integrity, Reliability, and Availability. +* **Flexible routing:** Use channel filters, message headers, and metadata to selectively route only the messages that need processing. +* **Multiple integration methods:** Choose webhooks, queues, or streaming based on your architecture and scale requirements. +* **Proven at scale:** Ably processes over 500 million messages per day for customers, with [serverless scalability](/docs/platform/architecture/platform-scalability) that eliminates infrastructure management. +* **No vendor lock-in:** Integrate with any external service, using any language or platform that can handle HTTP requests or consume from queues. + +Because Ably Chat rooms use Pub/Sub channels as their underlying transport, you can leverage all of Ably's [platform integrations](/docs/platform/integrations) to extend your chat application. + +## Key considerations + +Consider the following when integrating external systems with Ably Chat: + +* **Processing latency:** Integration adds latency to the message flow. Webhooks offer the lowest latency, while queues add asynchronous processing time. Design your system to handle the expected processing time. +* **Avoiding infinite loops:** When your integration responds back to chat, ensure responses don't trigger the same integration rule. Use separate channels, skip integration flags, or message headers to break potential loops. +* **Scale and reliability:** Different integration methods offer different reliability guarantees. Webhooks have limited retry windows, while queues provide guaranteed delivery with dead letter queues. +* **External system limitations:** Your external service may have rate limits, processing constraints, or availability issues. Implement retry logic, circuit breakers, and caching to handle these limitations gracefully. +* **Cost optimization:** Only process messages that require processing. Use channel filters and message headers to minimize unnecessary integration executions. +* **Security:** Validate webhook signatures, use token authentication with limited capabilities, and never embed API keys in client code. + +## Implementation options + +With your strategy in mind, choose the technical approach that fits your needs: + +1. Using [outbound webhooks](#webhook). [HTTP endpoint](/docs/platform/integrations/webhooks/generic), [AWS Lambda](/docs/platform/integrations/webhooks/lambda), and others. +2. Using [Ably queues](#ably-queue). +3. Using [outbound streaming](#outbound-streaming). Stream to your own [Kafka](/docs/platform/integrations/streaming/kafka), [Kinesis](/docs/platform/integrations/streaming/kinesis), and others. + +## Understanding the integration pattern + +Ably Chat integrations follow a message-driven architecture that enables bidirectional communication between your chat application and external systems. + +### Core concepts + +* **Chat rooms use Pub/Sub channels:** Each chat room is built on an Ably channel, enabling the full range of Ably's integration capabilities. +* **Message structure:** Chat messages are encoded as Pub/Sub messages with a specific structure. See the [Chat integrations documentation](/docs/chat/integrations) for details on the mapping. +* **Integration rules:** Configure rules that filter which messages trigger integrations based on channel patterns, event types, or other criteria. +* **Bidirectional flow:** Integrations can both receive messages from chat and publish responses back to the chat room. + +### Integration flow + +The typical integration flow works as follows: + +1. A user sends a message to a chat room, optionally including headers, metadata, or trigger patterns. +2. The message is published to the Ably Chat channel. +3. An integration rule evaluates the message based on channel name, message headers, or other criteria. +4. If the rule matches, Ably forwards the message to your external system via the configured integration method. +5. Your external system processes the message (AI inference, translation, business logic, etc.). +6. The external system publishes a response back to an Ably Chat channel. +7. All subscribed clients receive the response in real-time. + +**Diagram:** *Integration flow showing: Client → Ably Chat → Integration Rule → External System → Response → Ably Chat → Clients* + +### Triggering integrations + +You have several options for controlling which messages trigger integrations: + +**Channel naming patterns:** +Use consistent channel naming conventions to route messages selectively. For example, use `chat:support:*` for support channels or `chat:commands:*` for command processing. + +**Message headers:** +Add custom headers to messages that should trigger integration processing: + +```javascript +await room.messages.send({ + text: '/ask What are your business hours?', + headers: { + 'x-integration-trigger': 'assistant', + 'x-priority': 'high' + } +}); +``` + +**Message metadata:** +Use structured metadata for more complex trigger logic: + +```javascript +await room.messages.send({ + text: 'Please translate this message', + metadata: { + action: 'translate', + targetLanguage: 'es', + sourceLanguage: 'en' + } +}); +``` + +**Content patterns:** +Your integration can detect patterns in the message text itself, such as slash commands (`/weather London`) or specific keywords. + +## Filtering rooms and event types + +Integrations allow you to filter which Ably channels are forwarded to your external system using a regular expression on the channel name. This is a simple way to reduce the volume of messages you need to process by only receiving messages from the chat rooms you are interested in. Use a common prefix in the name of chat rooms that you want to trigger an integration for, and use the prefix as the filter. + +Use `channel.message` as the event type for all integration types. This will forward all messages published to the relevant channels and exclude presence messages and channel lifecycle messages. + +Select **enveloped messages** when setting up your integrations to receive all the metadata about the message, including the `serial`, `version`, and `extras` (which include the [`headers`](/docs/chat/rooms/messages#structure) of a chat message). + +## Decoding and processing messages + +Regardless of the delivery mechanism, you will need to decode the received messages into Chat messages. Details of the mapping from Ably Pub/Sub messages to Chat messages are available in the [chat integrations](/docs/chat/integrations) documentation. + +After performing the decoding to get your chat `Message` object, you can proceed to process it through your external system. + +### Enveloped messages + +With enveloping enabled (the default), Ably wraps messages in additional metadata: + +```javascript +{ + "source": "channel.message", + "appId": "your-app-id", + "channel": "chat:support:room-123", + "site": "eu-west-1-A", + "ruleId": "integration-rule-id", + "messages": [ + { + "id": "message-id", + "name": "event-name", + "connectionId": "connection-id", + "timestamp": 1234567890, + "data": { + "text": "Message content", + "metadata": {} + }, + "extras": { + "headers": { + "x-integration-trigger": "assistant" + } + } + } + ] +} +``` + +### Processing messages with the Ably SDK + +The recommended approach is to use the Ably SDK to decode messages, which handles encoding, data types, and converts numeric action values to strings: + +```javascript +const Ably = require('ably'); + +// In your webhook handler or queue consumer +function processIntegrationPayload(envelopeData) { + // Decode messages using Ably SDK + const decodedMessages = Ably.Realtime.Message.fromEncodedArray( + envelopeData.messages + ); + + for (const msg of decodedMessages) { + // Extract chat message fields + const text = msg.data?.text; + const metadata = msg.data?.metadata || {}; + const headers = msg.extras?.headers || {}; + + // Check for integration triggers + if (headers['x-integration-trigger']) { + const trigger = headers['x-integration-trigger']; + processMessage(text, trigger, metadata, envelopeData.channel); + } + } +} +``` + +### Message action types + +Chat messages have different action types that indicate their lifecycle state: + +* `message.create` (numeric: `0`) - A new message +* `message.update` (numeric: `1`) - An edited message +* `message.delete` (numeric: `2`) - A deleted message +* `message.summary` (numeric: `4`) - A message with reaction updates + +If you're not using an Ably SDK, you'll need to convert these numeric values to their string representations. See the [Chat integrations documentation](/docs/chat/integrations) for full details on message structure mapping. + +## Using a webhook + +Ably can forward messages to your external system via a webhook. This is the simplest to set up and offers the lowest latency. This section covers the simple HTTP endpoint webhook, but the same principles apply to other webhook integrations such as AWS Lambda, Azure Function, Google Cloud Function, and others. + +Read the guide on [outbound webhooks](/docs/platform/integrations/webhooks) for more details on how to set up a webhook with Ably for the platform of your choice. + +**Benefits:** +* Easiest to set up and understand +* No infrastructure to manage with serverless functions +* Direct HTTP invocation with automatic retries +* Native support for major cloud providers +* Lowest latency option + +**You need to consider:** +* **Cold start latency:** Serverless functions may have cold start delays. Consider keeping functions warm for latency-sensitive use cases. +* **Limited retry window:** Ably will retry delivering the message to your webhook, but only for a short period. Monitor the [`[meta]log` channel](/docs/platform/errors#meta) for delivery failures. +* **Scale limits:** Webhook scale depends on your function's concurrency configuration. Ensure your function can handle expected message volumes. +* **[At-least-once delivery](/docs/platform/architecture/idempotency#protocol-support-for-exactly-once-delivery):** You need to handle duplicate messages. Implement idempotency using message IDs. +* **Ordering:** Messages can arrive out-of-order. Use `serial` and `version.serial` properties to order them correctly if needed. + +### Example webhook handler + +```javascript +const Ably = require('ably'); +const crypto = require('crypto'); + +// Webhook handler (e.g., AWS Lambda, Express.js) +exports.handler = async (event) => { + // Verify webhook signature for security + const signature = event.headers['x-ably-signature']; + if (!verifySignature(event.body, signature)) { + return { statusCode: 401, body: 'Invalid signature' }; + } + + // Parse the enveloped message + const envelope = JSON.parse(event.body); + const channel = envelope.channel; + + // Decode messages using Ably SDK + const messages = Ably.Realtime.Message.fromEncodedArray(envelope.messages); + + for (const msg of messages) { + // Check if message should be processed + const headers = msg.extras?.headers || {}; + if (headers['x-integration-trigger'] === 'assistant') { + const text = msg.data?.text; + const response = await processWithExternalSystem(text); + await sendResponseToChat(channel, response); + } + } + + return { statusCode: 200 }; +}; + +function verifySignature(payload, signature) { + const expectedSignature = crypto + .createHmac('sha256', process.env.WEBHOOK_SECRET) + .update(payload) + .digest('hex'); + return crypto.timingSafeEqual( + Buffer.from(signature), + Buffer.from(expectedSignature) + ); +} +``` + +## Using an Ably queue + +Ably can forward messages from chat room channels to an [Ably Queue](/docs/platform/integrations/queues), which you can then consume from your own servers to process messages through your external system and respond back to chat. Read the guide on [Ably queues](/docs/platform/integrations/queues) for more details on how to set up the queue integration with Ably. + +Ably ensures that each message is delivered to only one consumer even if multiple consumers are connected. + +**Benefits:** +* Guaranteed at-least-once delivery with dead letter queue support +* Backpressure management via queue depth monitoring +* No message loss if your consumers temporarily disconnect +* Scale consumers independently by running multiple instances +* Ably manages the queue infrastructure for you + +**You need to consider:** +* **Processing latency:** Asynchronous queue processing adds latency compared to webhooks. Consider if your use case can tolerate this delay. +* **Queue limits:** Each message has a time-to-live (TTL) in the queue. The default and maximum is 60 minutes. Monitor queue depth during peak times. +* **Consumer scaling:** During peak times you may need to scale up your consumers to avoid overloading the queue past the maximum queue length allowed. +* **Dead letter queue:** Oldest messages are dropped if the maximum queue length is exceeded. Always consume messages from the [dead letter queue](/docs/platform/integrations/queues#deadletter) to monitor errors. +* **Consumer infrastructure:** You need to manage and maintain your consumer servers or containers that process messages from the queue. + +### Example queue consumer + +```javascript +const Ably = require('ably'); +const amqp = require('amqplib'); + +async function consumeFromQueue() { + // Connect to Ably Queue using AMQP + const url = 'amqps://APPID.KEYID:SECRET@us-east-1-a-queue.ably.io/shared'; + const conn = await amqp.connect(url); + const channel = await conn.createChannel(); + + // Consume messages from the queue + await channel.consume('your-app-id:chat-integrations', async (item) => { + try { + // Parse enveloped message + const envelope = JSON.parse(item.content.toString()); + const chatChannel = envelope.channel; + + // Decode messages using Ably SDK + const messages = Ably.Realtime.Message.fromEncodedArray(envelope.messages); + + for (const msg of messages) { + // Process message with external system + const text = msg.data?.text; + const headers = msg.extras?.headers || {}; + + if (headers['x-integration-trigger']) { + const response = await processWithExternalSystem(text); + await sendResponseToChat(chatChannel, response); + } + } + + // ACK message to remove from queue + channel.ack(item); + } catch (error) { + console.error('Processing failed:', error); + // NACK with requeue=false to send to dead letter queue + channel.nack(item, false, false); + } + }); +} +``` + +## Using outbound streaming + +Ably can stream messages directly to your own queueing or streaming service: Kafka, Kinesis, AMQP, SQS, Pulsar. Read the guide on [outbound streaming](/docs/platform/integrations/streaming) for more details on how to set up the streaming integration with Ably for the service of your choice. + +**Benefits:** +* Massive scale (millions of messages/second) +* Multiple consumers can process the same stream +* Integration with existing data infrastructure +* Message replay capability for reprocessing +* Use your existing queue system to process messages from Ably + +**You need to consider:** +* **Infrastructure complexity:** You need to maintain and be responsible for a reliable streaming system. If you don't already have such a system, it increases complexity. +* **Consistency:** If your streaming system is not reachable, messages may be lost. Errors can be seen in the [`[meta]log` channel](/docs/platform/errors#meta). +* **Processing latency:** Similar to queues, streaming adds asynchronous processing latency. +* **Setup complexity:** Most complex integration method to set up and operate. + +## Responding back to chat + +The critical consideration when responding back to chat is avoiding infinite loops where responses trigger the same integration rule. This section explains strategies to safely publish responses back to the chat room. + +### Strategy 1: Use separate channels + +Respond to a different channel pattern that doesn't trigger the integration: + +```javascript +const Ably = require('ably'); + +async function sendResponseToChat(originalChannel, responseText) { + // Use REST client for efficient one-off publishing + const ably = new Ably.Rest({ key: process.env.ABLY_API_KEY }); + + // Respond to a different channel pattern + // Integration triggers on 'chat:*', respond to 'chat-responses:*' + const responseChannel = originalChannel.replace('chat:', 'chat-responses:'); + + await ably.channels.get(responseChannel).publish('assistant-reply', { + text: responseText, + metadata: { + source: 'integration', + timestamp: Date.now() + } + }); +} +``` + +Your client subscribes to both the original chat channel and the response channel: + +```javascript +// Subscribe to regular chat messages +room.messages.subscribe((event) => { + displayMessage(event.message); +}); + +// Subscribe to integration responses on a separate channel +const realtimeClient = new Ably.Realtime({ key: 'your-token' }); +const responseChannel = realtimeClient.channels.get('chat-responses:room-123'); + +responseChannel.subscribe('assistant-reply', (message) => { + displayIntegrationResponse(message.data); +}); +``` + +### Strategy 2: Skip integrations on response messages + +Use Ably's [skip integrations](/docs/platform/integrations/skip-integrations) feature to publish messages that bypass integration rules: + +```javascript +async function sendResponseToChat(channel, responseText) { + const ably = new Ably.Rest({ key: process.env.ABLY_API_KEY }); + + await ably.channels.get(channel).publish({ + name: 'assistant-reply', + data: { text: responseText }, + extras: { + push: { + skipIntegrations: true + } + } + }); +} +``` + +### Strategy 3: Use message headers to filter + +Configure your integration rule to exclude messages with specific headers: + +```javascript +// Send response with a header that your integration rule filters out +await ably.channels.get(channel).publish({ + name: 'assistant-reply', + data: { text: responseText }, + extras: { + headers: { + 'x-skip-integration': 'true' + } + } +}); +``` + +Then configure your integration rule's channel filter to exclude these messages based on the header presence. + +## Common integration patterns + +The following patterns demonstrate practical integration scenarios. These are generic examples that you can adapt to your specific external services. + +### Pattern 1: Command processing + +Process slash commands that trigger server-side logic: + +**User experience:** +1. User types `/weather London` in chat +2. Integration detects the command pattern +3. External service fetches weather data +4. Response published back to chat: "Weather in London: 18°C, Partly cloudy" + +**Implementation:** + +```javascript +function detectCommand(text) { + const commandPattern = /^\/(\w+)\s*(.*)$/; + const match = text.match(commandPattern); + + if (match) { + return { + command: match[1], + args: match[2] + }; + } + return null; +} + +async function handleCommand(text, channel) { + const cmd = detectCommand(text); + if (!cmd) return; + + switch (cmd.command) { + case 'weather': + const weather = await fetchWeatherData(cmd.args); + await publishResponse(channel, `Weather in ${cmd.args}: ${weather}`); + break; + + case 'help': + await publishResponse(channel, 'Available commands: /weather, /help'); + break; + + default: + await publishResponse(channel, `Unknown command: /${cmd.command}`); + } +} +``` + +### Pattern 2: Content enrichment + +Automatically enhance messages with metadata: + +**User experience:** +1. User shares a URL in chat +2. Integration detects the URL +3. Metadata fetched (title, description, preview image) +4. Enriched message published with preview data + +**Implementation:** + +```javascript +async function enrichMessageWithURLs(text, channel) { + const urlPattern = /(https?:\/\/[^\s]+)/g; + const urls = text.match(urlPattern); + + if (!urls) return; + + for (const url of urls) { + const metadata = await fetchURLMetadata(url); + + await publishEnrichedContent(channel, { + type: 'url-preview', + url: url, + title: metadata.title, + description: metadata.description, + image: metadata.image, + originalMessageText: text + }); + } +} + +async function fetchURLMetadata(url) { + // Call metadata extraction service + const response = await fetch( + `https://metadata-api.example.com?url=${encodeURIComponent(url)}` + ); + return response.json(); +} +``` + +### Pattern 3: Multi-step workflows + +Handle complex processes with status updates: + +**User experience:** +1. User initiates action: `/analyze attachment-id` +2. System responds: "Starting analysis..." +3. Progress updates: "Processing... 50% complete" +4. Final result: "Analysis complete: [results]" + +**Implementation:** + +```javascript +async function handleAnalysisWorkflow(attachmentId, channel) { + const workflowId = generateWorkflowId(); + + // Step 1: Acknowledge start + await publishStatus(channel, workflowId, { + status: 'started', + message: 'Starting analysis...' + }); + + try { + // Step 2: Fetch attachment + const attachment = await fetchAttachment(attachmentId); + await publishStatus(channel, workflowId, { + status: 'progress', + message: 'Processing...', + progress: 25 + }); + + // Step 3: Analyze + const results = await performAnalysis(attachment); + await publishStatus(channel, workflowId, { + status: 'progress', + message: 'Finalizing...', + progress: 75 + }); + + // Step 4: Complete + await publishStatus(channel, workflowId, { + status: 'complete', + message: 'Analysis complete', + results: results + }); + + } catch (error) { + await publishStatus(channel, workflowId, { + status: 'failed', + message: `Analysis failed: ${error.message}` + }); + } +} +``` + +## Best practices for production + +When building production-ready integrations, follow these best practices: + +### Error handling and retry logic + +Implement exponential backoff for failed external API calls: + +```javascript +async function retryWithBackoff(fn, maxRetries = 3) { + let delay = 1000; // Start with 1 second + + for (let i = 0; i < maxRetries; i++) { + try { + return await fn(); + } catch (error) { + if (i === maxRetries - 1) throw error; + + await new Promise(resolve => setTimeout(resolve, delay)); + delay *= 2; // Exponential backoff + } + } +} +``` + +### Idempotency + +Design your handlers to safely process duplicate messages: + +```javascript +const processedMessages = new Set(); + +function isProcessed(messageId) { + return processedMessages.has(messageId); +} + +function markProcessed(messageId) { + processedMessages.add(messageId); + // Implement cleanup to prevent memory leaks + // e.g., use LRU cache or periodic cleanup +} + +async function processMessage(message) { + if (isProcessed(message.id)) { + console.log('Message already processed, skipping'); + return; + } + + // Process the message + await handleMessage(message); + + // Mark as processed + markProcessed(message.id); +} +``` + +### Rate limiting and caching + +Cache frequent requests to reduce latency and cost: + +```javascript +const cache = new Map(); + +async function getCachedTranslation(text, targetLang) { + const cacheKey = `${targetLang}:${text}`; + + if (cache.has(cacheKey)) { + return cache.get(cacheKey); + } + + const translation = await translateText(text, targetLang); + cache.set(cacheKey, translation); + + return translation; +} +``` + +### Security + +Secure your webhook endpoints and validate signatures: + +```javascript +const crypto = require('crypto'); + +function verifyWebhookSignature(payload, signature, secret) { + const expectedSignature = crypto + .createHmac('sha256', secret) + .update(payload) + .digest('hex'); + + return crypto.timingSafeEqual( + Buffer.from(signature), + Buffer.from(expectedSignature) + ); +} +``` + +### Monitoring + +Monitor integration health using the `[meta]log` channel: + +```javascript +const metaChannel = ably.channels.get('[meta]log:integration'); + +metaChannel.subscribe((message) => { + if (message.data.level === 'error') { + console.error('Integration error:', message.data); + // Send alert to your monitoring system + sendAlert('Integration error detected', message.data); + } +}); +``` + +## Testing your integration + +Validate your integration before deploying to production: + +### Local testing + +Use webhook testing tools to develop locally: + +* **[ngrok](https://ngrok.com/):** Create a secure tunnel to your local development server +* **[webhook.site](https://webhook.site/):** Inspect webhook payloads without writing code + +```bash +# Example: Expose local server with ngrok +ngrok http 3000 + +# Use the generated URL in your Ably integration rule +# Example: https://abc123.ngrok.io/webhook +``` + +### Load testing + +Simulate production message volumes to verify scale: + +```javascript +async function loadTest(roomName, messageCount) { + const room = await chatClient.rooms.get(roomName); + + console.log(`Sending ${messageCount} messages...`); + + for (let i = 0; i < messageCount; i++) { + await room.messages.send({ + text: `Load test message ${i}`, + headers: { 'x-load-test': 'true' } + }); + + // Add delay to simulate realistic traffic + if (i % 100 === 0) { + await new Promise(resolve => setTimeout(resolve, 100)); + } + } + + console.log('Load test complete'); +} +``` + +## Production checklist + +Before deploying your integration to production, verify: + +- [ ] Integration rule filters correctly match target channels +- [ ] External system authenticated and authorized properly +- [ ] Response messages configured to avoid infinite loops (separate channels or skip flag) +- [ ] Error handling and logging implemented +- [ ] Retry logic and exponential backoff in place +- [ ] Monitoring and alerting configured + - [ ] Subscribe to `[meta]log` channel + - [ ] Track error rates and latency + - [ ] Monitor queue depths (if using queues) + - [ ] Alert on dead letter queue messages +- [ ] Load tested at expected scale (+ 50% buffer) +- [ ] Cost estimation validated based on message volume +- [ ] Security review completed + - [ ] No API keys embedded in client code + - [ ] TLS/HTTPS enabled for all connections + - [ ] Webhook signatures validated + - [ ] Secrets stored in secure management system +- [ ] Idempotency handling implemented +- [ ] Documentation for your team on how integration works +- [ ] Rollback plan prepared in case of issues +- [ ] Test in staging environment before production + +## Next steps + +Explore related documentation to deepen your integration knowledge: + +* [Chat SDK documentation](/docs/chat) - Comprehensive guide to Ably Chat features +* [Platform Integrations](/docs/platform/integrations) - Detailed setup for webhooks, queues, and streaming +* [Chat Integrations](/docs/chat/integrations) - Technical reference for Chat message structure +* [Export Chat Data](/docs/guides/chat/export-chat) - Guide for storing all chat messages long-term +* [Architecture Overview](/docs/platform/architecture) - Learn how Ably achieves scale and reliability +* [Authentication](/docs/auth) - Security best practices for production apps +* [Chat Moderation](/docs/chat/moderation) - Filter and moderate chat content + +For custom integration requirements or questions, [contact Ably support](https://ably.com/support). \ No newline at end of file From 8e3bae564cce7885efc0be2a3d2e681dd00d1140 Mon Sep 17 00:00:00 2001 From: Steven Lindsay Date: Mon, 15 Dec 2025 11:49:35 +0000 Subject: [PATCH 02/33] wip --- src/data/nav/chat.ts | 4 + .../guides/chat/external-integrations.mdx | 203 ++++++++++-------- 2 files changed, 119 insertions(+), 88 deletions(-) diff --git a/src/data/nav/chat.ts b/src/data/nav/chat.ts index ad8ca06013..a156162cbd 100644 --- a/src/data/nav/chat.ts +++ b/src/data/nav/chat.ts @@ -200,6 +200,10 @@ export default { name: 'Export chat messages', link: '/docs/guides/chat/export-chat', }, + { + name: 'Export and process chat messages', + link: '/docs/guides/chat/external-integrations', + }, ], }, ], diff --git a/src/pages/docs/guides/chat/external-integrations.mdx b/src/pages/docs/guides/chat/external-integrations.mdx index aa1d3196cb..758993f1d6 100644 --- a/src/pages/docs/guides/chat/external-integrations.mdx +++ b/src/pages/docs/guides/chat/external-integrations.mdx @@ -26,7 +26,7 @@ Unlike [chat moderation](/docs/chat/moderation), which filters or blocks content Ably Chat integrations are built on Ably's proven platform architecture, designed to handle integration workloads at any scale: * **Reliable message delivery:** Every integration benefits from Ably's [four pillars of dependability](/docs/platform/architecture): Performance, Integrity, Reliability, and Availability. -* **Flexible routing:** Use channel filters, message headers, and metadata to selectively route only the messages that need processing. +* **Flexible routing:** Use channel filters to selectively route only the messages on channels that need processing to your integration. * **Multiple integration methods:** Choose webhooks, queues, or streaming based on your architecture and scale requirements. * **Proven at scale:** Ably processes over 500 million messages per day for customers, with [serverless scalability](/docs/platform/architecture/platform-scalability) that eliminates infrastructure management. * **No vendor lock-in:** Integrate with any external service, using any language or platform that can handle HTTP requests or consume from queues. @@ -38,7 +38,7 @@ Because Ably Chat rooms use Pub/Sub channels as their underlying transport, you Consider the following when integrating external systems with Ably Chat: * **Processing latency:** Integration adds latency to the message flow. Webhooks offer the lowest latency, while queues add asynchronous processing time. Design your system to handle the expected processing time. -* **Avoiding infinite loops:** When your integration responds back to chat, ensure responses don't trigger the same integration rule. Use separate channels, skip integration flags, or message headers to break potential loops. +* **Avoiding infinite loops:** When your integration responds back to chat, ensure responses don't trigger the same integration rule. Use separate channels or the [privileged skip integration flag](/docs/platform/integrations/skip-integrations) to break potential loops. * **Scale and reliability:** Different integration methods offer different reliability guarantees. Webhooks have limited retry windows, while queues provide guaranteed delivery with dead letter queues. * **External system limitations:** Your external service may have rate limits, processing constraints, or availability issues. Implement retry logic, circuit breakers, and caching to handle these limitations gracefully. * **Cost optimization:** Only process messages that require processing. Use channel filters and message headers to minimize unnecessary integration executions. @@ -60,7 +60,7 @@ Ably Chat integrations follow a message-driven architecture that enables bidirec * **Chat rooms use Pub/Sub channels:** Each chat room is built on an Ably channel, enabling the full range of Ably's integration capabilities. * **Message structure:** Chat messages are encoded as Pub/Sub messages with a specific structure. See the [Chat integrations documentation](/docs/chat/integrations) for details on the mapping. -* **Integration rules:** Configure rules that filter which messages trigger integrations based on channel patterns, event types, or other criteria. +* **Integration rules:** Configure rules that filter which messages trigger integrations based on channel name patterns and event types. * **Bidirectional flow:** Integrations can both receive messages from chat and publish responses back to the chat room. ### Integration flow @@ -69,7 +69,7 @@ The typical integration flow works as follows: 1. A user sends a message to a chat room, optionally including headers, metadata, or trigger patterns. 2. The message is published to the Ably Chat channel. -3. An integration rule evaluates the message based on channel name, message headers, or other criteria. +3. An integration rule evaluates the message based on channel name pattern matching. 4. If the rule matches, Ably forwards the message to your external system via the configured integration method. 5. Your external system processes the message (AI inference, translation, business logic, etc.). 6. The external system publishes a response back to an Ably Chat channel. @@ -79,40 +79,55 @@ The typical integration flow works as follows: ### Triggering integrations -You have several options for controlling which messages trigger integrations: +Integration rules are triggered by **channel name patterns** only. Use consistent channel naming conventions to route messages from specific chat rooms to your integration. **Channel naming patterns:** -Use consistent channel naming conventions to route messages selectively. For example, use `chat:support:*` for support channels or `chat:commands:*` for command processing. -**Message headers:** -Add custom headers to messages that should trigger integration processing: +Use typical chat room naming patterns like `chat:support` for support channels or `chat:group:123` for group chats. The integration rule's channel filter is a regular expression that matches against the channel name. ```javascript -await room.messages.send({ - text: '/ask What are your business hours?', - headers: { - 'x-integration-trigger': 'assistant', - 'x-priority': 'high' - } -}); +// Messages sent to these channels will trigger an integration +// if your rule's channel filter is: ^chat:support.* +await room.messages.send({ text: 'Need help' }); // channel: chat:support + +// Or for a specific group +await room.messages.send({ text: 'Hello' }); // channel: chat:group:123 + +// Messages sent to other channel patterns will NOT trigger the integration +await room.messages.send({ text: 'Hi' }); // channel: chat:general (if filter is ^chat:support.*) ``` -**Message metadata:** -Use structured metadata for more complex trigger logic: +**Filtering messages within your integration:** + +Your external system receives all messages from channels matching the integration rule's filter. Your service is responsible for examining each message and deciding which ones to process: ```javascript -await room.messages.send({ - text: 'Please translate this message', - metadata: { - action: 'translate', - targetLanguage: 'es', - sourceLanguage: 'en' +// In your webhook handler or queue consumer +function processIntegrationPayload(envelopeData) { + const messages = Ably.Realtime.Message.fromEncodedArray(envelopeData.messages); + + for (const msg of messages) { + const text = msg.data?.text; + const headers = msg.extras?.headers || {}; + const metadata = msg.data?.metadata || {}; + + // Your service decides what to process based on message content + if (text?.startsWith('/')) { + // Handle slash commands + processCommand(text, envelopeData.channel); + } else if (headers['x-needs-translation']) { + // Handle messages marked for translation + translateMessage(text, headers, envelopeData.channel); + } else if (metadata?.action === 'analyze') { + // Handle messages with specific metadata + analyzeMessage(text, metadata, envelopeData.channel); + } + // Otherwise, ignore this message } -}); +} ``` -**Content patterns:** -Your integration can detect patterns in the message text itself, such as slash commands (`/weather London`) or specific keywords. +This approach allows flexible message processing while using simple, logical channel naming conventions like `chat:support` or `chat:group:123`. ## Filtering rooms and event types @@ -172,13 +187,13 @@ function processIntegrationPayload(envelopeData) { const decodedMessages = Ably.Realtime.Message.fromEncodedArray( envelopeData.messages ); - + for (const msg of decodedMessages) { // Extract chat message fields const text = msg.data?.text; const metadata = msg.data?.metadata || {}; const headers = msg.extras?.headers || {}; - + // Check for integration triggers if (headers['x-integration-trigger']) { const trigger = headers['x-integration-trigger']; @@ -232,14 +247,14 @@ exports.handler = async (event) => { if (!verifySignature(event.body, signature)) { return { statusCode: 401, body: 'Invalid signature' }; } - + // Parse the enveloped message const envelope = JSON.parse(event.body); const channel = envelope.channel; - + // Decode messages using Ably SDK const messages = Ably.Realtime.Message.fromEncodedArray(envelope.messages); - + for (const msg of messages) { // Check if message should be processed const headers = msg.extras?.headers || {}; @@ -249,7 +264,7 @@ exports.handler = async (event) => { await sendResponseToChat(channel, response); } } - + return { statusCode: 200 }; }; @@ -296,28 +311,28 @@ async function consumeFromQueue() { const url = 'amqps://APPID.KEYID:SECRET@us-east-1-a-queue.ably.io/shared'; const conn = await amqp.connect(url); const channel = await conn.createChannel(); - + // Consume messages from the queue await channel.consume('your-app-id:chat-integrations', async (item) => { try { // Parse enveloped message const envelope = JSON.parse(item.content.toString()); const chatChannel = envelope.channel; - + // Decode messages using Ably SDK const messages = Ably.Realtime.Message.fromEncodedArray(envelope.messages); - + for (const msg of messages) { // Process message with external system const text = msg.data?.text; const headers = msg.extras?.headers || {}; - + if (headers['x-integration-trigger']) { const response = await processWithExternalSystem(text); await sendResponseToChat(chatChannel, response); } } - + // ACK message to remove from queue channel.ack(item); } catch (error) { @@ -350,7 +365,7 @@ Ably can stream messages directly to your own queueing or streaming service: Kaf The critical consideration when responding back to chat is avoiding infinite loops where responses trigger the same integration rule. This section explains strategies to safely publish responses back to the chat room. -### Strategy 1: Use separate channels +### Strategy 1: Use separate response channels Respond to a different channel pattern that doesn't trigger the integration: @@ -360,11 +375,12 @@ const Ably = require('ably'); async function sendResponseToChat(originalChannel, responseText) { // Use REST client for efficient one-off publishing const ably = new Ably.Rest({ key: process.env.ABLY_API_KEY }); - - // Respond to a different channel pattern - // Integration triggers on 'chat:*', respond to 'chat-responses:*' - const responseChannel = originalChannel.replace('chat:', 'chat-responses:'); - + + // Respond to a different channel that doesn't match your integration filter + // If integration triggers on 'chat:support' or 'chat:group:*', + // respond to 'responses:support' or 'responses:group:123' + const responseChannel = originalChannel.replace('chat:', 'responses:'); + await ably.channels.get(responseChannel).publish('assistant-reply', { text: responseText, metadata: { @@ -378,58 +394,68 @@ async function sendResponseToChat(originalChannel, responseText) { Your client subscribes to both the original chat channel and the response channel: ```javascript -// Subscribe to regular chat messages +// Subscribe to regular chat messages in the room room.messages.subscribe((event) => { displayMessage(event.message); }); // Subscribe to integration responses on a separate channel const realtimeClient = new Ably.Realtime({ key: 'your-token' }); -const responseChannel = realtimeClient.channels.get('chat-responses:room-123'); +const responseChannel = realtimeClient.channels.get('responses:support'); responseChannel.subscribe('assistant-reply', (message) => { displayIntegrationResponse(message.data); }); ``` -### Strategy 2: Skip integrations on response messages +### Strategy 2: Skip integration rules on response messages + +Use Ably's [skip integrations](/docs/platform/integrations/skip-integrations) feature to publish messages that bypass integration rules. -Use Ably's [skip integrations](/docs/platform/integrations/skip-integrations) feature to publish messages that bypass integration rules: + + +**Skip all integration rules:** ```javascript async function sendResponseToChat(channel, responseText) { + // Requires privileged-headers capability const ably = new Ably.Rest({ key: process.env.ABLY_API_KEY }); - + await ably.channels.get(channel).publish({ name: 'assistant-reply', data: { text: responseText }, extras: { - push: { - skipIntegrations: true + privileged: { + skipRule: '*' // Skip ALL integration rules } } }); } ``` -### Strategy 3: Use message headers to filter +**Skip specific integration rules:** -Configure your integration rule to exclude messages with specific headers: +If you only want to skip specific rules (e.g., the one that triggered your integration), you can provide the rule ID: ```javascript -// Send response with a header that your integration rule filters out -await ably.channels.get(channel).publish({ - name: 'assistant-reply', - data: { text: responseText }, - extras: { - headers: { - 'x-skip-integration': 'true' +async function sendResponseToChat(channel, responseText, ruleIdToSkip) { + const ably = new Ably.Rest({ key: process.env.ABLY_API_KEY }); + + await ably.channels.get(channel).publish({ + name: 'assistant-reply', + data: { text: responseText }, + extras: { + privileged: { + skipRule: [ruleIdToSkip] // Skip only this specific rule + } } - } -}); + }); +} ``` -Then configure your integration rule's channel filter to exclude these messages based on the header presence. +The rule ID is available in the integration webhook envelope's `ruleId` field, or you can find it in your Ably [dashboard](https://ably.com/dashboard) under Integrations. ## Common integration patterns @@ -451,7 +477,7 @@ Process slash commands that trigger server-side logic: function detectCommand(text) { const commandPattern = /^\/(\w+)\s*(.*)$/; const match = text.match(commandPattern); - + if (match) { return { command: match[1], @@ -464,17 +490,17 @@ function detectCommand(text) { async function handleCommand(text, channel) { const cmd = detectCommand(text); if (!cmd) return; - + switch (cmd.command) { case 'weather': const weather = await fetchWeatherData(cmd.args); await publishResponse(channel, `Weather in ${cmd.args}: ${weather}`); break; - + case 'help': await publishResponse(channel, 'Available commands: /weather, /help'); break; - + default: await publishResponse(channel, `Unknown command: /${cmd.command}`); } @@ -497,12 +523,12 @@ Automatically enhance messages with metadata: async function enrichMessageWithURLs(text, channel) { const urlPattern = /(https?:\/\/[^\s]+)/g; const urls = text.match(urlPattern); - + if (!urls) return; - + for (const url of urls) { const metadata = await fetchURLMetadata(url); - + await publishEnrichedContent(channel, { type: 'url-preview', url: url, @@ -538,13 +564,13 @@ Handle complex processes with status updates: ```javascript async function handleAnalysisWorkflow(attachmentId, channel) { const workflowId = generateWorkflowId(); - + // Step 1: Acknowledge start await publishStatus(channel, workflowId, { status: 'started', message: 'Starting analysis...' }); - + try { // Step 2: Fetch attachment const attachment = await fetchAttachment(attachmentId); @@ -553,7 +579,7 @@ async function handleAnalysisWorkflow(attachmentId, channel) { message: 'Processing...', progress: 25 }); - + // Step 3: Analyze const results = await performAnalysis(attachment); await publishStatus(channel, workflowId, { @@ -561,14 +587,14 @@ async function handleAnalysisWorkflow(attachmentId, channel) { message: 'Finalizing...', progress: 75 }); - + // Step 4: Complete await publishStatus(channel, workflowId, { status: 'complete', message: 'Analysis complete', results: results }); - + } catch (error) { await publishStatus(channel, workflowId, { status: 'failed', @@ -589,13 +615,13 @@ Implement exponential backoff for failed external API calls: ```javascript async function retryWithBackoff(fn, maxRetries = 3) { let delay = 1000; // Start with 1 second - + for (let i = 0; i < maxRetries; i++) { try { return await fn(); } catch (error) { if (i === maxRetries - 1) throw error; - + await new Promise(resolve => setTimeout(resolve, delay)); delay *= 2; // Exponential backoff } @@ -625,10 +651,10 @@ async function processMessage(message) { console.log('Message already processed, skipping'); return; } - + // Process the message await handleMessage(message); - + // Mark as processed markProcessed(message.id); } @@ -643,14 +669,14 @@ const cache = new Map(); async function getCachedTranslation(text, targetLang) { const cacheKey = `${targetLang}:${text}`; - + if (cache.has(cacheKey)) { return cache.get(cacheKey); } - + const translation = await translateText(text, targetLang); cache.set(cacheKey, translation); - + return translation; } ``` @@ -667,7 +693,7 @@ function verifyWebhookSignature(payload, signature, secret) { .createHmac('sha256', secret) .update(payload) .digest('hex'); - + return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expectedSignature) @@ -717,21 +743,21 @@ Simulate production message volumes to verify scale: ```javascript async function loadTest(roomName, messageCount) { const room = await chatClient.rooms.get(roomName); - + console.log(`Sending ${messageCount} messages...`); - + for (let i = 0; i < messageCount; i++) { await room.messages.send({ text: `Load test message ${i}`, headers: { 'x-load-test': 'true' } }); - + // Add delay to simulate realistic traffic if (i % 100 === 0) { await new Promise(resolve => setTimeout(resolve, 100)); } } - + console.log('Load test complete'); } ``` @@ -742,7 +768,8 @@ Before deploying your integration to production, verify: - [ ] Integration rule filters correctly match target channels - [ ] External system authenticated and authorized properly -- [ ] Response messages configured to avoid infinite loops (separate channels or skip flag) +- [ ] Response messages configured to avoid infinite loops (separate channels or privileged skipRule) +- [ ] API key has `privileged-headers` capability if using skipRule feature - [ ] Error handling and logging implemented - [ ] Retry logic and exponential backoff in place - [ ] Monitoring and alerting configured @@ -774,4 +801,4 @@ Explore related documentation to deepen your integration knowledge: * [Authentication](/docs/auth) - Security best practices for production apps * [Chat Moderation](/docs/chat/moderation) - Filter and moderate chat content -For custom integration requirements or questions, [contact Ably support](https://ably.com/support). \ No newline at end of file +For custom integration requirements or questions, [contact Ably support](https://ably.com/support). From 78cb87f481b7d4d3e077d4105e08ee5233f54aa6 Mon Sep 17 00:00:00 2001 From: Steven Lindsay Date: Mon, 15 Dec 2025 14:05:39 +0000 Subject: [PATCH 03/33] wip --- .../guides/chat/external-integrations.mdx | 428 ++++++++++-------- 1 file changed, 236 insertions(+), 192 deletions(-) diff --git a/src/pages/docs/guides/chat/external-integrations.mdx b/src/pages/docs/guides/chat/external-integrations.mdx index 758993f1d6..321619ec78 100644 --- a/src/pages/docs/guides/chat/external-integrations.mdx +++ b/src/pages/docs/guides/chat/external-integrations.mdx @@ -85,25 +85,36 @@ Integration rules are triggered by **channel name patterns** only. Use consisten Use typical chat room naming patterns like `chat:support` for support channels or `chat:group:123` for group chats. The integration rule's channel filter is a regular expression that matches against the channel name. + ```javascript -// Messages sent to these channels will trigger an integration +// Get a chat room - this determines which channel the message goes to +const supportRoom = await chatClient.rooms.get('chat:support'); +const groupRoom = await chatClient.rooms.get('chat:group:123'); +const generalRoom = await chatClient.rooms.get('chat:general'); + +// Messages sent to these rooms will trigger an integration // if your rule's channel filter is: ^chat:support.* -await room.messages.send({ text: 'Need help' }); // channel: chat:support +await supportRoom.messages.send({ text: 'Need help' }); // Or for a specific group -await room.messages.send({ text: 'Hello' }); // channel: chat:group:123 +await groupRoom.messages.send({ text: 'Hello' }); // Messages sent to other channel patterns will NOT trigger the integration -await room.messages.send({ text: 'Hi' }); // channel: chat:general (if filter is ^chat:support.*) +await generalRoom.messages.send({ text: 'Hi' }); // Won't trigger if filter is ^chat:support.* ``` + -**Filtering messages within your integration:** +**Using metadata and headers for message routing:** -Your external system receives all messages from channels matching the integration rule's filter. Your service is responsible for examining each message and deciding which ones to process: +Your external system receives all messages from channels matching the integration rule's filter. Use message headers or metadata to efficiently route messages to the appropriate handler: + ```javascript +const Ably = require('ably'); + // In your webhook handler or queue consumer function processIntegrationPayload(envelopeData) { + // Decode messages using Ably SDK const messages = Ably.Realtime.Message.fromEncodedArray(envelopeData.messages); for (const msg of messages) { @@ -111,23 +122,36 @@ function processIntegrationPayload(envelopeData) { const headers = msg.extras?.headers || {}; const metadata = msg.data?.metadata || {}; - // Your service decides what to process based on message content - if (text?.startsWith('/')) { - // Handle slash commands - processCommand(text, envelopeData.channel); - } else if (headers['x-needs-translation']) { - // Handle messages marked for translation - translateMessage(text, headers, envelopeData.channel); - } else if (metadata?.action === 'analyze') { - // Handle messages with specific metadata - analyzeMessage(text, metadata, envelopeData.channel); + // Use headers for efficient routing (no string parsing needed) + const action = headers['x-integration-action']; + + switch (action) { + case 'weather': + // Handle weather command + handleWeatherRequest(text, envelopeData.channel); + break; + + case 'translate': + // Handle translation request + const targetLang = headers['x-target-language'] || 'es'; + handleTranslation(text, targetLang, envelopeData.channel); + break; + + case 'analyze': + // Handle analysis request + handleAnalysis(text, metadata, envelopeData.channel); + break; + + default: + // Ignore messages without integration actions + break; } - // Otherwise, ignore this message } } ``` + -This approach allows flexible message processing while using simple, logical channel naming conventions like `chat:support` or `chat:group:123`. +This approach is more efficient than string parsing and provides clean separation between user-visible content and integration routing logic. ## Filtering rooms and event types @@ -463,80 +487,140 @@ The following patterns demonstrate practical integration scenarios. These are ge ### Pattern 1: Command processing -Process slash commands that trigger server-side logic: +Process commands that trigger server-side logic using metadata/headers: **User experience:** -1. User types `/weather London` in chat -2. Integration detects the command pattern -3. External service fetches weather data -4. Response published back to chat: "Weather in London: 18°C, Partly cloudy" +1. User types `/weather London` in the chat UI +2. Client app detects the slash command and extracts the action and arguments +3. Message sent with `x-integration-action` header set to 'weather' +4. Integration processes the request +5. Response published back to chat: "Weather in London: 18°C, Partly cloudy" -**Implementation:** +**Client-side implementation:** + ```javascript -function detectCommand(text) { - const commandPattern = /^\/(\w+)\s*(.*)$/; - const match = text.match(commandPattern); - - if (match) { - return { - command: match[1], - args: match[2] - }; - } - return null; +// Detect slash commands in your UI +function handleMessageInput(inputText, command) { + // Send message with integration action header + await room.messages.send({ + text: inputText, + headers: { + 'x-integration-action': command, // 'weather', 'help', etc. + } + }); } +``` + + +**Server-side handler:** + + +```javascript +const Ably = require('ably'); + +async function handleIntegrationMessage(envelopeData) { + const messages = Ably.Realtime.Message.fromEncodedArray(envelopeData.messages); + + for (const msg of messages) { + const action = msg.extras?.headers?.['x-integration-action']; + if (!action) continue; // Skip messages without integration action -async function handleCommand(text, channel) { - const cmd = detectCommand(text); - if (!cmd) return; + const text = msg.data?.text || ''; + const channel = envelopeData.channel; - switch (cmd.command) { - case 'weather': - const weather = await fetchWeatherData(cmd.args); - await publishResponse(channel, `Weather in ${cmd.args}: ${weather}`); - break; + switch (action) { + case 'weather': + const weather = await fetchWeatherData(text); + await publishResponse(channel, `Weather in ${text}: ${weather}`); + break; - case 'help': - await publishResponse(channel, 'Available commands: /weather, /help'); - break; + case 'help': + await publishResponse(channel, 'Available commands: /weather, /help'); + break; - default: - await publishResponse(channel, `Unknown command: /${cmd.command}`); + default: + await publishResponse(channel, `Unknown command: /${action}`); + } } } + +async function fetchWeatherData(location) { + // Call weather API + const response = await fetch(`https://api.weather.example.com?q=${location}`); + const data = await response.json(); + return `${data.temp}°C, ${data.conditions}`; +} ``` + ### Pattern 2: Content enrichment -Automatically enhance messages with metadata: +Automatically enhance messages with metadata using headers: **User experience:** 1. User shares a URL in chat -2. Integration detects the URL -3. Metadata fetched (title, description, preview image) +2. Client detects the URL and adds enrichment header +3. Integration fetches metadata (title, description, preview image) 4. Enriched message published with preview data -**Implementation:** +**Client-side implementation:** + ```javascript -async function enrichMessageWithURLs(text, channel) { +function detectURLs(text) { const urlPattern = /(https?:\/\/[^\s]+)/g; - const urls = text.match(urlPattern); - - if (!urls) return; + return text.match(urlPattern) || []; +} - for (const url of urls) { - const metadata = await fetchURLMetadata(url); +async function sendMessageWithURLDetection(text) { + const urls = detectURLs(text); - await publishEnrichedContent(channel, { - type: 'url-preview', - url: url, - title: metadata.title, - description: metadata.description, - image: metadata.image, - originalMessageText: text + if (urls.length > 0) { + // Request URL enrichment via integration + await room.messages.send({ + text: text, + headers: { + 'x-integration-action': 'enrich-urls', + 'x-urls': JSON.stringify(urls) + } }); + } else { + // Regular message + await room.messages.send({ text: text }); + } +} +``` + + +**Server-side handler:** + + +```javascript +const Ably = require('ably'); + +async function handleURLEnrichment(envelopeData) { + const messages = Ably.Realtime.Message.fromEncodedArray(envelopeData.messages); + + for (const msg of messages) { + const action = msg.extras?.headers?.['x-integration-action']; + if (action !== 'enrich-urls') continue; + + const urls = JSON.parse(msg.extras.headers['x-urls'] || '[]'); + const channel = envelopeData.channel; + + for (const url of urls) { + const metadata = await fetchURLMetadata(url); + + await publishEnrichedContent(channel, { + type: 'url-preview', + url: url, + title: metadata.title, + description: metadata.description, + image: metadata.image, + originalMessageText: msg.data?.text + }); + } } } @@ -548,10 +632,11 @@ async function fetchURLMetadata(url) { return response.json(); } ``` + ### Pattern 3: Multi-step workflows -Handle complex processes with status updates: +Handle complex processes with status updates using metadata: **User experience:** 1. User initiates action: `/analyze attachment-id` @@ -559,163 +644,122 @@ Handle complex processes with status updates: 3. Progress updates: "Processing... 50% complete" 4. Final result: "Analysis complete: [results]" -**Implementation:** +**Client-side implementation:** + ```javascript -async function handleAnalysisWorkflow(attachmentId, channel) { - const workflowId = generateWorkflowId(); - - // Step 1: Acknowledge start - await publishStatus(channel, workflowId, { - status: 'started', - message: 'Starting analysis...' +// User initiates analysis command +async function initiateAnalysis(attachmentId) { + await room.messages.send({ + text: attachmentId, + headers: { + 'x-integration-action': 'analyze', + 'x-workflow-id': generateWorkflowId() + } }); - - try { - // Step 2: Fetch attachment - const attachment = await fetchAttachment(attachmentId); - await publishStatus(channel, workflowId, { - status: 'progress', - message: 'Processing...', - progress: 25 - }); - - // Step 3: Analyze - const results = await performAnalysis(attachment); - await publishStatus(channel, workflowId, { - status: 'progress', - message: 'Finalizing...', - progress: 75 - }); - - // Step 4: Complete - await publishStatus(channel, workflowId, { - status: 'complete', - message: 'Analysis complete', - results: results - }); - - } catch (error) { - await publishStatus(channel, workflowId, { - status: 'failed', - message: `Analysis failed: ${error.message}` - }); - } } ``` + -## Best practices for production +**Server-side handler:** -When building production-ready integrations, follow these best practices: + +```javascript +const Ably = require('ably'); -### Error handling and retry logic +async function handleAnalysisWorkflow(envelopeData) { + const messages = Ably.Realtime.Message.fromEncodedArray(envelopeData.messages); -Implement exponential backoff for failed external API calls: + for (const msg of messages) { + const action = msg.extras?.headers?.['x-integration-action']; + if (action !== 'analyze') continue; -```javascript -async function retryWithBackoff(fn, maxRetries = 3) { - let delay = 1000; // Start with 1 second + const attachmentId = msg.data?.text; + const workflowId = msg.extras.headers['x-workflow-id']; + const channel = envelopeData.channel; + + // Step 1: Acknowledge start + await publishStatus(channel, workflowId, { + status: 'started', + message: 'Starting analysis...' + }); - for (let i = 0; i < maxRetries; i++) { try { - return await fn(); - } catch (error) { - if (i === maxRetries - 1) throw error; + // Step 2: Fetch attachment + const attachment = await fetchAttachment(attachmentId); + await publishStatus(channel, workflowId, { + status: 'progress', + message: 'Processing...', + progress: 25 + }); + + // Step 3: Analyze + const results = await performAnalysis(attachment); + await publishStatus(channel, workflowId, { + status: 'progress', + message: 'Finalizing...', + progress: 75 + }); + + // Step 4: Complete + await publishStatus(channel, workflowId, { + status: 'complete', + message: 'Analysis complete', + results: results + }); - await new Promise(resolve => setTimeout(resolve, delay)); - delay *= 2; // Exponential backoff + } catch (error) { + await publishStatus(channel, workflowId, { + status: 'failed', + message: `Analysis failed: ${error.message}` + }); } } } ``` + -### Idempotency - -Design your handlers to safely process duplicate messages: - -```javascript -const processedMessages = new Set(); +## Best practices for production -function isProcessed(messageId) { - return processedMessages.has(messageId); -} +When building production-ready integrations, follow these best practices: -function markProcessed(messageId) { - processedMessages.add(messageId); - // Implement cleanup to prevent memory leaks - // e.g., use LRU cache or periodic cleanup -} +### Error handling and resilience -async function processMessage(message) { - if (isProcessed(message.id)) { - console.log('Message already processed, skipping'); - return; - } +* **Retry logic:** Implement exponential backoff for failed external API calls to handle transient failures gracefully +* **Circuit breakers:** Prevent cascading failures by temporarily stopping requests to failing external services +* **Timeouts:** Set appropriate timeouts for external API calls to avoid indefinite blocking +* **Graceful degradation:** Design your system to handle external service failures without breaking the entire integration +* See [Ably's architecture documentation](/docs/platform/architecture/fault-tolerance) for resilience patterns - // Process the message - await handleMessage(message); +### Idempotency - // Mark as processed - markProcessed(message.id); -} -``` +* **Message deduplication:** Track processed message IDs to safely handle Ably's [at-least-once delivery](/docs/platform/architecture/idempotency#protocol-support-for-exactly-once-delivery) +* **Idempotent operations:** Design your external system operations to be safely retryable +* **Cleanup strategy:** Implement cleanup for tracked message IDs to prevent memory leaks (LRU cache, TTL-based cleanup) ### Rate limiting and caching -Cache frequent requests to reduce latency and cost: - -```javascript -const cache = new Map(); - -async function getCachedTranslation(text, targetLang) { - const cacheKey = `${targetLang}:${text}`; - - if (cache.has(cacheKey)) { - return cache.get(cacheKey); - } - - const translation = await translateText(text, targetLang); - cache.set(cacheKey, translation); - - return translation; -} -``` +* **Cache responses:** Cache frequent external API responses to reduce latency and costs +* **Rate limits:** Respect external service rate limits with throttling and queuing +* **Batching:** Consider batching multiple operations to reduce external API calls +* **CDN integration:** Use CDNs for static content enrichment (images, metadata) ### Security -Secure your webhook endpoints and validate signatures: +* **Webhook signatures:** Always validate webhook signatures using Ably's provided signature headers +* **Token authentication:** Use short-lived [JWT tokens](/docs/auth/token#jwt) with minimal capabilities for client connections +* **API key security:** Never embed API keys in client code; use server-side endpoints to generate tokens +* **TLS/HTTPS:** Ensure all connections use TLS encryption +* **Secret management:** Store API keys and secrets in secure management systems (AWS Secrets Manager, Azure Key Vault, etc.) +* See [Ably's authentication documentation](/docs/auth) for security best practices -```javascript -const crypto = require('crypto'); +### Monitoring and observability -function verifyWebhookSignature(payload, signature, secret) { - const expectedSignature = crypto - .createHmac('sha256', secret) - .update(payload) - .digest('hex'); - - return crypto.timingSafeEqual( - Buffer.from(signature), - Buffer.from(expectedSignature) - ); -} -``` - -### Monitoring - -Monitor integration health using the `[meta]log` channel: - -```javascript -const metaChannel = ably.channels.get('[meta]log:integration'); - -metaChannel.subscribe((message) => { - if (message.data.level === 'error') { - console.error('Integration error:', message.data); - // Send alert to your monitoring system - sendAlert('Integration error detected', message.data); - } -}); -``` +* **[Meta channel](/docs/metadata-stats/metadata/subscribe) monitoring:** Subscribe to `[meta]log:integration` to track integration errors +* **Dead letter queues:** Monitor [dead letter queues](/docs/platform/integrations/queues#deadletter) if using Ably Queues +* **Metrics tracking:** Monitor error rates, latency, throughput, and external service response times +* **Alerting:** Set up alerts for integration failures, queue depth issues, and performance degradation +* **Logging:** Implement structured logging with correlation IDs for debugging ## Testing your integration From 2ef7e6fae2b1fe38a303892e84d9ea5f918ad352 Mon Sep 17 00:00:00 2001 From: Steven Lindsay Date: Mon, 15 Dec 2025 15:18:14 +0000 Subject: [PATCH 04/33] wip --- .../guides/chat/external-integrations.mdx | 650 +++--------------- 1 file changed, 106 insertions(+), 544 deletions(-) diff --git a/src/pages/docs/guides/chat/external-integrations.mdx b/src/pages/docs/guides/chat/external-integrations.mdx index 321619ec78..ff63d9c435 100644 --- a/src/pages/docs/guides/chat/external-integrations.mdx +++ b/src/pages/docs/guides/chat/external-integrations.mdx @@ -77,105 +77,56 @@ The typical integration flow works as follows: **Diagram:** *Integration flow showing: Client → Ably Chat → Integration Rule → External System → Response → Ably Chat → Clients* -### Triggering integrations +## Filtering rooms and event types + +Integrations allow you to filter which Ably channels are forwarded to your external system using a regular expression on the channel name. This is a simple way to reduce the volume of messages you need to process by only receiving messages from the chat rooms you are interested in. + +### Setting up integration rules + +When configuring an integration rule in your Ably dashboard: + +* **Channel filter:** Use a regular expression to match channel names. For example, `^chat:support.*` will match all channels starting with `chat:support` +* **Event type:** Use `channel.message` for all integration types. This forwards all messages published to relevant channels and excludes presence messages and channel lifecycle messages +* **Enveloped messages:** Enable this to receive all metadata about the message, including the `serial`, `version`, and `extras` (which include the [`headers`](/docs/chat/rooms/messages#structure) of a chat message) -Integration rules are triggered by **channel name patterns** only. Use consistent channel naming conventions to route messages from specific chat rooms to your integration. +### Understanding channel names and room names -**Channel naming patterns:** +When using the Ably Chat SDK, the room name (e.g., `chat:support`) automatically becomes the underlying channel name with a `::$chat` suffix (e.g., `chat:support::$chat`). Integration rules match against the full channel name, but you don't need to include the `::$chat` suffix in your filter pattern - it's handled automatically when using the Chat SDK. -Use typical chat room naming patterns like `chat:support` for support channels or `chat:group:123` for group chats. The integration rule's channel filter is a regular expression that matches against the channel name. +**Example room and channel naming:** ```javascript -// Get a chat room - this determines which channel the message goes to +// Get a chat room - the room name becomes the channel name with ::$chat suffix const supportRoom = await chatClient.rooms.get('chat:support'); +// Underlying channel: chat:support::$chat + const groupRoom = await chatClient.rooms.get('chat:group:123'); -const generalRoom = await chatClient.rooms.get('chat:general'); +// Underlying channel: chat:group:123::$chat // Messages sent to these rooms will trigger an integration // if your rule's channel filter is: ^chat:support.* await supportRoom.messages.send({ text: 'Need help' }); -// Or for a specific group -await groupRoom.messages.send({ text: 'Hello' }); - // Messages sent to other channel patterns will NOT trigger the integration +const generalRoom = await chatClient.rooms.get('chat:general'); await generalRoom.messages.send({ text: 'Hi' }); // Won't trigger if filter is ^chat:support.* ``` -**Using metadata and headers for message routing:** - -Your external system receives all messages from channels matching the integration rule's filter. Use message headers or metadata to efficiently route messages to the appropriate handler: - - -```javascript -const Ably = require('ably'); - -// In your webhook handler or queue consumer -function processIntegrationPayload(envelopeData) { - // Decode messages using Ably SDK - const messages = Ably.Realtime.Message.fromEncodedArray(envelopeData.messages); - - for (const msg of messages) { - const text = msg.data?.text; - const headers = msg.extras?.headers || {}; - const metadata = msg.data?.metadata || {}; - - // Use headers for efficient routing (no string parsing needed) - const action = headers['x-integration-action']; - - switch (action) { - case 'weather': - // Handle weather command - handleWeatherRequest(text, envelopeData.channel); - break; - - case 'translate': - // Handle translation request - const targetLang = headers['x-target-language'] || 'es'; - handleTranslation(text, targetLang, envelopeData.channel); - break; - - case 'analyze': - // Handle analysis request - handleAnalysis(text, metadata, envelopeData.channel); - break; - - default: - // Ignore messages without integration actions - break; - } - } -} -``` - - -This approach is more efficient than string parsing and provides clean separation between user-visible content and integration routing logic. - -## Filtering rooms and event types - -Integrations allow you to filter which Ably channels are forwarded to your external system using a regular expression on the channel name. This is a simple way to reduce the volume of messages you need to process by only receiving messages from the chat rooms you are interested in. Use a common prefix in the name of chat rooms that you want to trigger an integration for, and use the prefix as the filter. - -Use `channel.message` as the event type for all integration types. This will forward all messages published to the relevant channels and exclude presence messages and channel lifecycle messages. - -Select **enveloped messages** when setting up your integrations to receive all the metadata about the message, including the `serial`, `version`, and `extras` (which include the [`headers`](/docs/chat/rooms/messages#structure) of a chat message). - ## Decoding and processing messages -Regardless of the delivery mechanism, you will need to decode the received messages into Chat messages. Details of the mapping from Ably Pub/Sub messages to Chat messages are available in the [chat integrations](/docs/chat/integrations) documentation. - -After performing the decoding to get your chat `Message` object, you can proceed to process it through your external system. +When your integration receives messages, you need to decode them and extract the relevant information. This section walks through each step of processing integration payloads. -### Enveloped messages +### Understanding enveloped messages -With enveloping enabled (the default), Ably wraps messages in additional metadata: +With enveloping enabled (the default), Ably wraps messages in additional metadata like so: ```javascript { "source": "channel.message", "appId": "your-app-id", - "channel": "chat:support:room-123", + "channel": "chat:support::$chat", "site": "eu-west-1-A", "ruleId": "integration-rule-id", "messages": [ @@ -190,7 +141,7 @@ With enveloping enabled (the default), Ably wraps messages in additional metadat }, "extras": { "headers": { - "x-integration-trigger": "assistant" + "x-integration-action": "weather" } } } @@ -198,45 +149,89 @@ With enveloping enabled (the default), Ably wraps messages in additional metadat } ``` -### Processing messages with the Ably SDK +### Extracting the room name -The recommended approach is to use the Ably SDK to decode messages, which handles encoding, data types, and converts numeric action values to strings: +Your integration receives the full channel name including the `::$chat` suffix. To send responses back to the same room, extract the room name by removing the suffix: + +```javascript +// In your webhook handler or queue consumer +function processIntegrationPayload(envelopeData) { + // Extract room name from channel name + const channelName = envelopeData.channel; // e.g., "chat:support::$chat" + const roomName = channelName.replace('::$chat', ''); // e.g., "chat:support" +} +``` + + +### Extracting message data + +The Ably SDK provides methods to decode messages from the enveloped payload. From there, you can extract the text content, headers, and metadata: + + ```javascript const Ably = require('ably'); -// In your webhook handler or queue consumer function processIntegrationPayload(envelopeData) { // Decode messages using Ably SDK - const decodedMessages = Ably.Realtime.Message.fromEncodedArray( - envelopeData.messages - ); + const decodedMessages = Ably.Realtime.Message.fromEncodedArray(envelopeData.messages); for (const msg of decodedMessages) { - // Extract chat message fields + // Extract text content const text = msg.data?.text; - const metadata = msg.data?.metadata || {}; + + // Extract headers (used for routing and control) const headers = msg.extras?.headers || {}; + const action = headers['x-integration-action']; - // Check for integration triggers - if (headers['x-integration-trigger']) { - const trigger = headers['x-integration-trigger']; - processMessage(text, trigger, metadata, envelopeData.channel); - } + // Extract metadata (custom structured data) + const metadata = msg.data?.metadata || {}; } } ``` + + +### Routing based on headers + +You can use custom headers to control how messages are processed by your integration. For example, you might define an `x-integration-action` header to specify different processing actions: -### Message action types + +```javascript +const Ably = require('ably'); + +// In your webhook handler or queue consumer +async function processMessage(roomName, text, ) { + for (const msg of decodedMessages) { + // Extract message data + const text = msg.data?.text; + const headers = msg.extras?.headers || {}; + const metadata = msg.data?.metadata || {}; + + // Step 4: Route based on headers (no string parsing needed) + const action = headers['x-integration-action']; + + switch (action) { + case 'weather': + await handleWeatherRequest(text, roomName); + break; -Chat messages have different action types that indicate their lifecycle state: + case 'translate': + const targetLang = headers['x-target-language'] || 'es'; + await handleTranslation(text, targetLang, roomName); + break; -* `message.create` (numeric: `0`) - A new message -* `message.update` (numeric: `1`) - An edited message -* `message.delete` (numeric: `2`) - A deleted message -* `message.summary` (numeric: `4`) - A message with reaction updates + case 'ai-assist': + await handleAIAssistant(text, roomName); + break; -If you're not using an Ably SDK, you'll need to convert these numeric values to their string representations. See the [Chat integrations documentation](/docs/chat/integrations) for full details on message structure mapping. + default: + // Ignore messages without integration actions + break; + } + } +} +``` + ## Using a webhook @@ -258,52 +253,6 @@ Read the guide on [outbound webhooks](/docs/platform/integrations/webhooks) for * **[At-least-once delivery](/docs/platform/architecture/idempotency#protocol-support-for-exactly-once-delivery):** You need to handle duplicate messages. Implement idempotency using message IDs. * **Ordering:** Messages can arrive out-of-order. Use `serial` and `version.serial` properties to order them correctly if needed. -### Example webhook handler - -```javascript -const Ably = require('ably'); -const crypto = require('crypto'); - -// Webhook handler (e.g., AWS Lambda, Express.js) -exports.handler = async (event) => { - // Verify webhook signature for security - const signature = event.headers['x-ably-signature']; - if (!verifySignature(event.body, signature)) { - return { statusCode: 401, body: 'Invalid signature' }; - } - - // Parse the enveloped message - const envelope = JSON.parse(event.body); - const channel = envelope.channel; - - // Decode messages using Ably SDK - const messages = Ably.Realtime.Message.fromEncodedArray(envelope.messages); - - for (const msg of messages) { - // Check if message should be processed - const headers = msg.extras?.headers || {}; - if (headers['x-integration-trigger'] === 'assistant') { - const text = msg.data?.text; - const response = await processWithExternalSystem(text); - await sendResponseToChat(channel, response); - } - } - - return { statusCode: 200 }; -}; - -function verifySignature(payload, signature) { - const expectedSignature = crypto - .createHmac('sha256', process.env.WEBHOOK_SECRET) - .update(payload) - .digest('hex'); - return crypto.timingSafeEqual( - Buffer.from(signature), - Buffer.from(expectedSignature) - ); -} -``` - ## Using an Ably queue Ably can forward messages from chat room channels to an [Ably Queue](/docs/platform/integrations/queues), which you can then consume from your own servers to process messages through your external system and respond back to chat. Read the guide on [Ably queues](/docs/platform/integrations/queues) for more details on how to set up the queue integration with Ably. @@ -324,50 +273,6 @@ Ably ensures that each message is delivered to only one consumer even if multipl * **Dead letter queue:** Oldest messages are dropped if the maximum queue length is exceeded. Always consume messages from the [dead letter queue](/docs/platform/integrations/queues#deadletter) to monitor errors. * **Consumer infrastructure:** You need to manage and maintain your consumer servers or containers that process messages from the queue. -### Example queue consumer - -```javascript -const Ably = require('ably'); -const amqp = require('amqplib'); - -async function consumeFromQueue() { - // Connect to Ably Queue using AMQP - const url = 'amqps://APPID.KEYID:SECRET@us-east-1-a-queue.ably.io/shared'; - const conn = await amqp.connect(url); - const channel = await conn.createChannel(); - - // Consume messages from the queue - await channel.consume('your-app-id:chat-integrations', async (item) => { - try { - // Parse enveloped message - const envelope = JSON.parse(item.content.toString()); - const chatChannel = envelope.channel; - - // Decode messages using Ably SDK - const messages = Ably.Realtime.Message.fromEncodedArray(envelope.messages); - - for (const msg of messages) { - // Process message with external system - const text = msg.data?.text; - const headers = msg.extras?.headers || {}; - - if (headers['x-integration-trigger']) { - const response = await processWithExternalSystem(text); - await sendResponseToChat(chatChannel, response); - } - } - - // ACK message to remove from queue - channel.ack(item); - } catch (error) { - console.error('Processing failed:', error); - // NACK with requeue=false to send to dead letter queue - channel.nack(item, false, false); - } - }); -} -``` - ## Using outbound streaming Ably can stream messages directly to your own queueing or streaming service: Kafka, Kinesis, AMQP, SQS, Pulsar. Read the guide on [outbound streaming](/docs/platform/integrations/streaming) for more details on how to set up the streaming integration with Ably for the service of your choice. @@ -387,337 +292,39 @@ Ably can stream messages directly to your own queueing or streaming service: Kaf ## Responding back to chat -The critical consideration when responding back to chat is avoiding infinite loops where responses trigger the same integration rule. This section explains strategies to safely publish responses back to the chat room. - -### Strategy 1: Use separate response channels - -Respond to a different channel pattern that doesn't trigger the integration: - -```javascript -const Ably = require('ably'); - -async function sendResponseToChat(originalChannel, responseText) { - // Use REST client for efficient one-off publishing - const ably = new Ably.Rest({ key: process.env.ABLY_API_KEY }); - - // Respond to a different channel that doesn't match your integration filter - // If integration triggers on 'chat:support' or 'chat:group:*', - // respond to 'responses:support' or 'responses:group:123' - const responseChannel = originalChannel.replace('chat:', 'responses:'); - - await ably.channels.get(responseChannel).publish('assistant-reply', { - text: responseText, - metadata: { - source: 'integration', - timestamp: Date.now() - } - }); -} -``` - -Your client subscribes to both the original chat channel and the response channel: - -```javascript -// Subscribe to regular chat messages in the room -room.messages.subscribe((event) => { - displayMessage(event.message); -}); - -// Subscribe to integration responses on a separate channel -const realtimeClient = new Ably.Realtime({ key: 'your-token' }); -const responseChannel = realtimeClient.channels.get('responses:support'); - -responseChannel.subscribe('assistant-reply', (message) => { - displayIntegrationResponse(message.data); -}); -``` - -### Strategy 2: Skip integration rules on response messages - -Use Ably's [skip integrations](/docs/platform/integrations/skip-integrations) feature to publish messages that bypass integration rules. - - - -**Skip all integration rules:** - -```javascript -async function sendResponseToChat(channel, responseText) { - // Requires privileged-headers capability - const ably = new Ably.Rest({ key: process.env.ABLY_API_KEY }); - - await ably.channels.get(channel).publish({ - name: 'assistant-reply', - data: { text: responseText }, - extras: { - privileged: { - skipRule: '*' // Skip ALL integration rules - } - } - }); -} -``` - -**Skip specific integration rules:** - -If you only want to skip specific rules (e.g., the one that triggered your integration), you can provide the rule ID: - -```javascript -async function sendResponseToChat(channel, responseText, ruleIdToSkip) { - const ably = new Ably.Rest({ key: process.env.ABLY_API_KEY }); - - await ably.channels.get(channel).publish({ - name: 'assistant-reply', - data: { text: responseText }, - extras: { - privileged: { - skipRule: [ruleIdToSkip] // Skip only this specific rule - } - } - }); -} -``` - -The rule ID is available in the integration webhook envelope's `ruleId` field, or you can find it in your Ably [dashboard](https://ably.com/dashboard) under Integrations. - -## Common integration patterns - -The following patterns demonstrate practical integration scenarios. These are generic examples that you can adapt to your specific external services. - -### Pattern 1: Command processing +After processing messages through your external system, you will typically want to publish responses back to the chat room. This could be answers from an AI assistant, translated text, enriched content, or status updates. +To avoid triggering the integration rule on the response message, Use Ably's [skip integrations](/docs/platform/integrations/skip-integrations) feature to publish messages that bypass integration rules. -Process commands that trigger server-side logic using metadata/headers: - -**User experience:** -1. User types `/weather London` in the chat UI -2. Client app detects the slash command and extracts the action and arguments -3. Message sent with `x-integration-action` header set to 'weather' -4. Integration processes the request -5. Response published back to chat: "Weather in London: 18°C, Partly cloudy" - -**Client-side implementation:** - - -```javascript -// Detect slash commands in your UI -function handleMessageInput(inputText, command) { - // Send message with integration action header - await room.messages.send({ - text: inputText, - headers: { - 'x-integration-action': command, // 'weather', 'help', etc. - } - }); -} -``` - - -**Server-side handler:** +**Note:** Your API key or token must have the [`privileged-headers`](/docs/auth/capabilities#capability-operations) capability to apply the skip integration flag. ```javascript const Ably = require('ably'); -async function handleIntegrationMessage(envelopeData) { - const messages = Ably.Realtime.Message.fromEncodedArray(envelopeData.messages); - - for (const msg of messages) { - const action = msg.extras?.headers?.['x-integration-action']; - if (!action) continue; // Skip messages without integration action - - const text = msg.data?.text || ''; - const channel = envelopeData.channel; - - switch (action) { - case 'weather': - const weather = await fetchWeatherData(text); - await publishResponse(channel, `Weather in ${text}: ${weather}`); - break; - - case 'help': - await publishResponse(channel, 'Available commands: /weather, /help'); - break; +async function sendResponseToChat(envelopeData, responseText) { + // Extract the room name from the channel name + const channelName = envelopeData.channel; // e.g., "chat:support::$chat" + const roomName = channelName.replace('::$chat', ''); // e.g., "chat:support" - default: - await publishResponse(channel, `Unknown command: /${action}`); - } - } -} - -async function fetchWeatherData(location) { - // Call weather API - const response = await fetch(`https://api.weather.example.com?q=${location}`); - const data = await response.json(); - return `${data.temp}°C, ${data.conditions}`; -} -``` - - -### Pattern 2: Content enrichment - -Automatically enhance messages with metadata using headers: - -**User experience:** -1. User shares a URL in chat -2. Client detects the URL and adds enrichment header -3. Integration fetches metadata (title, description, preview image) -4. Enriched message published with preview data - -**Client-side implementation:** - - -```javascript -function detectURLs(text) { - const urlPattern = /(https?:\/\/[^\s]+)/g; - return text.match(urlPattern) || []; -} - -async function sendMessageWithURLDetection(text) { - const urls = detectURLs(text); - - if (urls.length > 0) { - // Request URL enrichment via integration - await room.messages.send({ - text: text, - headers: { - 'x-integration-action': 'enrich-urls', - 'x-urls': JSON.stringify(urls) - } - }); - } else { - // Regular message - await room.messages.send({ text: text }); - } -} -``` - - -**Server-side handler:** - - -```javascript -const Ably = require('ably'); - -async function handleURLEnrichment(envelopeData) { - const messages = Ably.Realtime.Message.fromEncodedArray(envelopeData.messages); - - for (const msg of messages) { - const action = msg.extras?.headers?.['x-integration-action']; - if (action !== 'enrich-urls') continue; - - const urls = JSON.parse(msg.extras.headers['x-urls'] || '[]'); - const channel = envelopeData.channel; - - for (const url of urls) { - const metadata = await fetchURLMetadata(url); - - await publishEnrichedContent(channel, { - type: 'url-preview', - url: url, - title: metadata.title, - description: metadata.description, - image: metadata.image, - originalMessageText: msg.data?.text - }); - } - } -} - -async function fetchURLMetadata(url) { - // Call metadata extraction service - const response = await fetch( - `https://metadata-api.example.com?url=${encodeURIComponent(url)}` - ); - return response.json(); -} -``` - - -### Pattern 3: Multi-step workflows - -Handle complex processes with status updates using metadata: - -**User experience:** -1. User initiates action: `/analyze attachment-id` -2. System responds: "Starting analysis..." -3. Progress updates: "Processing... 50% complete" -4. Final result: "Analysis complete: [results]" - -**Client-side implementation:** + // Initialize Chat client and get the room + const chatClient = new Ably.Chat(realtime); + const room = await chatClient.rooms.get(roomName); - -```javascript -// User initiates analysis command -async function initiateAnalysis(attachmentId) { + // Send response message skipping all integration rules await room.messages.send({ - text: attachmentId, - headers: { - 'x-integration-action': 'analyze', - 'x-workflow-id': generateWorkflowId() + text: responseText + }, { + extras: { + privileged: {' + skipRule: [ruleIdToSkip] // To skip all rules use '*' instead of an array + } } }); } ``` -**Server-side handler:** - - -```javascript -const Ably = require('ably'); - -async function handleAnalysisWorkflow(envelopeData) { - const messages = Ably.Realtime.Message.fromEncodedArray(envelopeData.messages); - - for (const msg of messages) { - const action = msg.extras?.headers?.['x-integration-action']; - if (action !== 'analyze') continue; - - const attachmentId = msg.data?.text; - const workflowId = msg.extras.headers['x-workflow-id']; - const channel = envelopeData.channel; - - // Step 1: Acknowledge start - await publishStatus(channel, workflowId, { - status: 'started', - message: 'Starting analysis...' - }); - - try { - // Step 2: Fetch attachment - const attachment = await fetchAttachment(attachmentId); - await publishStatus(channel, workflowId, { - status: 'progress', - message: 'Processing...', - progress: 25 - }); - - // Step 3: Analyze - const results = await performAnalysis(attachment); - await publishStatus(channel, workflowId, { - status: 'progress', - message: 'Finalizing...', - progress: 75 - }); - - // Step 4: Complete - await publishStatus(channel, workflowId, { - status: 'complete', - message: 'Analysis complete', - results: results - }); - - } catch (error) { - await publishStatus(channel, workflowId, { - status: 'failed', - message: `Analysis failed: ${error.message}` - }); - } - } -} -``` - +The rule ID is available in the integration webhook envelope's `ruleId` field, or you can find it in your Ably [dashboard](https://ably.com/dashboard) under Integrations. ## Best practices for production @@ -761,51 +368,6 @@ When building production-ready integrations, follow these best practices: * **Alerting:** Set up alerts for integration failures, queue depth issues, and performance degradation * **Logging:** Implement structured logging with correlation IDs for debugging -## Testing your integration - -Validate your integration before deploying to production: - -### Local testing - -Use webhook testing tools to develop locally: - -* **[ngrok](https://ngrok.com/):** Create a secure tunnel to your local development server -* **[webhook.site](https://webhook.site/):** Inspect webhook payloads without writing code - -```bash -# Example: Expose local server with ngrok -ngrok http 3000 - -# Use the generated URL in your Ably integration rule -# Example: https://abc123.ngrok.io/webhook -``` - -### Load testing - -Simulate production message volumes to verify scale: - -```javascript -async function loadTest(roomName, messageCount) { - const room = await chatClient.rooms.get(roomName); - - console.log(`Sending ${messageCount} messages...`); - - for (let i = 0; i < messageCount; i++) { - await room.messages.send({ - text: `Load test message ${i}`, - headers: { 'x-load-test': 'true' } - }); - - // Add delay to simulate realistic traffic - if (i % 100 === 0) { - await new Promise(resolve => setTimeout(resolve, 100)); - } - } - - console.log('Load test complete'); -} -``` - ## Production checklist Before deploying your integration to production, verify: From a34811a779f0d91056a1054cefa187eb458f03a1 Mon Sep 17 00:00:00 2001 From: Steven Lindsay Date: Mon, 15 Dec 2025 17:18:57 +0000 Subject: [PATCH 05/33] wip --- src/data/nav/chat.ts | 2 +- .../guides/chat/external-integrations.mdx | 104 +++++++----------- 2 files changed, 43 insertions(+), 63 deletions(-) diff --git a/src/data/nav/chat.ts b/src/data/nav/chat.ts index a156162cbd..64164729b1 100644 --- a/src/data/nav/chat.ts +++ b/src/data/nav/chat.ts @@ -201,7 +201,7 @@ export default { link: '/docs/guides/chat/export-chat', }, { - name: 'Export and process chat messages', + name: 'Integrate external services', link: '/docs/guides/chat/external-integrations', }, ], diff --git a/src/pages/docs/guides/chat/external-integrations.mdx b/src/pages/docs/guides/chat/external-integrations.mdx index ff63d9c435..d805e7372e 100644 --- a/src/pages/docs/guides/chat/external-integrations.mdx +++ b/src/pages/docs/guides/chat/external-integrations.mdx @@ -14,7 +14,7 @@ Integrating external systems with Ably Chat enables you to extend your chat appl * **AI-powered assistants:** Process commands or questions through language models and respond with helpful information. * **Real-time translation:** Automatically translate messages for multilingual chat rooms. -* **Content enrichment:** Expand URLs into rich previews, detect and process mentions, or add contextual information. +* **Notifications:** Detect and process mentions, or other notifications. * **Analytics and monitoring:** Analyze sentiment, track engagement metrics, or identify trending topics. * **Workflow automation:** Trigger external business processes based on chat activity. * **Command processing:** Handle slash commands that invoke server-side logic. @@ -37,12 +37,10 @@ Because Ably Chat rooms use Pub/Sub channels as their underlying transport, you Consider the following when integrating external systems with Ably Chat: -* **Processing latency:** Integration adds latency to the message flow. Webhooks offer the lowest latency, while queues add asynchronous processing time. Design your system to handle the expected processing time. * **Avoiding infinite loops:** When your integration responds back to chat, ensure responses don't trigger the same integration rule. Use separate channels or the [privileged skip integration flag](/docs/platform/integrations/skip-integrations) to break potential loops. -* **Scale and reliability:** Different integration methods offer different reliability guarantees. Webhooks have limited retry windows, while queues provide guaranteed delivery with dead letter queues. +* **Scale and reliability:** Different integration methods offer different reliability guarantees. Webhooks are simpler to implement but have limited retry windows, queues provide at-least-once delivery with dead letter queues, and streaming offers massive scale but requires more infrastructure management. * **External system limitations:** Your external service may have rate limits, processing constraints, or availability issues. Implement retry logic, circuit breakers, and caching to handle these limitations gracefully. -* **Cost optimization:** Only process messages that require processing. Use channel filters and message headers to minimize unnecessary integration executions. -* **Security:** Validate webhook signatures, use token authentication with limited capabilities, and never embed API keys in client code. +* **Cost optimization:** Only process messages that require processing. Use channel filters to ensure you only receive messages from relevant chat rooms. ## Implementation options @@ -52,46 +50,47 @@ With your strategy in mind, choose the technical approach that fits your needs: 2. Using [Ably queues](#ably-queue). 3. Using [outbound streaming](#outbound-streaming). Stream to your own [Kafka](/docs/platform/integrations/streaming/kafka), [Kinesis](/docs/platform/integrations/streaming/kinesis), and others. -## Understanding the integration pattern +## Understanding integrations -Ably Chat integrations follow a message-driven architecture that enables bidirectional communication between your chat application and external systems. - -### Core concepts +Ably Chat rooms are built on Ably Pub/Sub channels, which means you can leverage Ably's integration capabilities to forward messages to external systems for processing: * **Chat rooms use Pub/Sub channels:** Each chat room is built on an Ably channel, enabling the full range of Ably's integration capabilities. * **Message structure:** Chat messages are encoded as Pub/Sub messages with a specific structure. See the [Chat integrations documentation](/docs/chat/integrations) for details on the mapping. * **Integration rules:** Configure rules that filter which messages trigger integrations based on channel name patterns and event types. -* **Bidirectional flow:** Integrations can both receive messages from chat and publish responses back to the chat room. +* **Bidirectional flow:** Integrations allow you to export messages to external systems, process them, and respond back to chat rooms in real-time. ### Integration flow The typical integration flow works as follows: -1. A user sends a message to a chat room, optionally including headers, metadata, or trigger patterns. -2. The message is published to the Ably Chat channel. -3. An integration rule evaluates the message based on channel name pattern matching. -4. If the rule matches, Ably forwards the message to your external system via the configured integration method. -5. Your external system processes the message (AI inference, translation, business logic, etc.). -6. The external system publishes a response back to an Ably Chat channel. -7. All subscribed clients receive the response in real-time. +1. A user sends a message to a chat room, optionally including headers or metadata to control processing externally. +2. An integration rule evaluates the message based on room name pattern matching. +3. If the rule matches, Ably forwards the message to your external system via the configured integration method. +4. Your external system processes the message (AI inference, translation, business logic, etc.). +5. The external system sends a response back to an Ably Chat room. +6. All subscribed clients receive the response in real-time. -**Diagram:** *Integration flow showing: Client → Ably Chat → Integration Rule → External System → Response → Ably Chat → Clients* +[/TODO/]: # (Add a visual showing somethign like *Client → Ably Chat → Integration Rule → External System → Response → Ably Chat → Clients*) ## Filtering rooms and event types -Integrations allow you to filter which Ably channels are forwarded to your external system using a regular expression on the channel name. This is a simple way to reduce the volume of messages you need to process by only receiving messages from the chat rooms you are interested in. +Integrations allow you to filter which Chat rooms are forwarded to your external system using a regular expression on the room name. +This is a simple way to reduce the volume of messages you need to process by only receiving messages from the chat rooms you are interested in. ### Setting up integration rules When configuring an integration rule in your Ably dashboard: -* **Channel filter:** Use a regular expression to match channel names. For example, `^chat:support.*` will match all channels starting with `chat:support` -* **Event type:** Use `channel.message` for all integration types. This forwards all messages published to relevant channels and excludes presence messages and channel lifecycle messages +* **Channel filter:** Use a regular expression to match channel names. For example, `^chat:support.*` will match all channels starting with `chat:support`. See [understanding channel names and room names](#understanding-channel-names-and-room-names) for details on how room names map to channel names. +* **Event type:** Use `channel.message` for all integration types. This forwards all chat messages published to relevant rooms and excludes presence messages and channel lifecycle messages * **Enveloped messages:** Enable this to receive all metadata about the message, including the `serial`, `version`, and `extras` (which include the [`headers`](/docs/chat/rooms/messages#structure) of a chat message) ### Understanding channel names and room names -When using the Ably Chat SDK, the room name (e.g., `chat:support`) automatically becomes the underlying channel name with a `::$chat` suffix (e.g., `chat:support::$chat`). Integration rules match against the full channel name, but you don't need to include the `::$chat` suffix in your filter pattern - it's handled automatically when using the Chat SDK. +Chat rooms are underpinned by Ably Pub/Sub channels, but with a specific suffix (`::$chat`) added to form the full channel name. +When using the Chat SDK to create or get a room, this is done automatically for you, you do not need to include the suffix yourself. + +Integration rules match against the full channel name, but you don't need to include the `::$chat` suffix in your filter pattern. **Example room and channel naming:** @@ -116,12 +115,13 @@ await generalRoom.messages.send({ text: 'Hi' }); // Won't trigger if filter is ^ ## Decoding and processing messages -When your integration receives messages, you need to decode them and extract the relevant information. This section walks through each step of processing integration payloads. +When your integration receives messages, you need to decode them and extract the relevant information. ### Understanding enveloped messages With enveloping enabled (the default), Ably wraps messages in additional metadata like so: + ```javascript { "source": "channel.message", @@ -148,6 +148,7 @@ With enveloping enabled (the default), Ably wraps messages in additional metadat ] } ``` + ### Extracting the room name @@ -166,13 +167,15 @@ function processIntegrationPayload(envelopeData) { ### Extracting message data -The Ably SDK provides methods to decode messages from the enveloped payload. From there, you can extract the text content, headers, and metadata: +The Ably SDK provides methods to decode messages from the enveloped payload. +From there, you can extract the text content, headers, and metadata and process them as needed. +You can use custom headers to control how messages are processed by your integration. For example, you might define an `x-integration-action` header to specify different processing actions: ```javascript const Ably = require('ably'); -function processIntegrationPayload(envelopeData) { +async function processIntegrationPayload(envelopeData) { // Decode messages using Ably SDK const decodedMessages = Ably.Realtime.Message.fromEncodedArray(envelopeData.messages); @@ -186,49 +189,30 @@ function processIntegrationPayload(envelopeData) { // Extract metadata (custom structured data) const metadata = msg.data?.metadata || {}; + + // Process each message + await processMessage(roomName, text, action); } } -``` - - -### Routing based on headers - -You can use custom headers to control how messages are processed by your integration. For example, you might define an `x-integration-action` header to specify different processing actions: - - -```javascript -const Ably = require('ably'); - -// In your webhook handler or queue consumer -async function processMessage(roomName, text, ) { - for (const msg of decodedMessages) { - // Extract message data - const text = msg.data?.text; - const headers = msg.extras?.headers || {}; - const metadata = msg.data?.metadata || {}; - - // Step 4: Route based on headers (no string parsing needed) - const action = headers['x-integration-action']; +async function processMessage(roomName, text, action) { switch (action) { case 'weather': await handleWeatherRequest(text, roomName); break; case 'translate': - const targetLang = headers['x-target-language'] || 'es'; - await handleTranslation(text, targetLang, roomName); + await handleTranslationRequest(text, roomName); break; case 'ai-assist': - await handleAIAssistant(text, roomName); + await handleAIAssistantRequest(text, roomName); break; default: // Ignore messages without integration actions break; } - } } ``` @@ -244,13 +228,12 @@ Read the guide on [outbound webhooks](/docs/platform/integrations/webhooks) for * No infrastructure to manage with serverless functions * Direct HTTP invocation with automatic retries * Native support for major cloud providers -* Lowest latency option **You need to consider:** * **Cold start latency:** Serverless functions may have cold start delays. Consider keeping functions warm for latency-sensitive use cases. * **Limited retry window:** Ably will retry delivering the message to your webhook, but only for a short period. Monitor the [`[meta]log` channel](/docs/platform/errors#meta) for delivery failures. * **Scale limits:** Webhook scale depends on your function's concurrency configuration. Ensure your function can handle expected message volumes. -* **[At-least-once delivery](/docs/platform/architecture/idempotency#protocol-support-for-exactly-once-delivery):** You need to handle duplicate messages. Implement idempotency using message IDs. +* **[At-least-once delivery](/docs/platform/architecture/idempotency#protocol-support-for-exactly-once-delivery):** Deduplication can be done by checking `serial` and `version.serial`. * **Ordering:** Messages can arrive out-of-order. Use `serial` and `version.serial` properties to order them correctly if needed. ## Using an Ably queue @@ -260,16 +243,15 @@ Ably can forward messages from chat room channels to an [Ably Queue](/docs/platf Ably ensures that each message is delivered to only one consumer even if multiple consumers are connected. **Benefits:** -* Guaranteed at-least-once delivery with dead letter queue support -* Backpressure management via queue depth monitoring -* No message loss if your consumers temporarily disconnect -* Scale consumers independently by running multiple instances -* Ably manages the queue infrastructure for you +* At-least-once delivery with dead letter queue support. +* No message loss if your consumers temporarily disconnect. +* Scale consumers independently by running multiple instances. +* Use multiple queues for different channels, filtering via regex on channel name. +* Ably manages the queue infrastructure for you. **You need to consider:** -* **Processing latency:** Asynchronous queue processing adds latency compared to webhooks. Consider if your use case can tolerate this delay. -* **Queue limits:** Each message has a time-to-live (TTL) in the queue. The default and maximum is 60 minutes. Monitor queue depth during peak times. -* **Consumer scaling:** During peak times you may need to scale up your consumers to avoid overloading the queue past the maximum queue length allowed. +* **Queue limits:** Each message has a time-to-live (TTL) in the queue. The default and maximum is 60 minutes. +* **Consumer scaling:** During peak times you may need to scale up your consumers to avoid going over the maximum queue length allowed. * **Dead letter queue:** Oldest messages are dropped if the maximum queue length is exceeded. Always consume messages from the [dead letter queue](/docs/platform/integrations/queues#deadletter) to monitor errors. * **Consumer infrastructure:** You need to manage and maintain your consumer servers or containers that process messages from the queue. @@ -281,13 +263,11 @@ Ably can stream messages directly to your own queueing or streaming service: Kaf * Massive scale (millions of messages/second) * Multiple consumers can process the same stream * Integration with existing data infrastructure -* Message replay capability for reprocessing * Use your existing queue system to process messages from Ably **You need to consider:** * **Infrastructure complexity:** You need to maintain and be responsible for a reliable streaming system. If you don't already have such a system, it increases complexity. * **Consistency:** If your streaming system is not reachable, messages may be lost. Errors can be seen in the [`[meta]log` channel](/docs/platform/errors#meta). -* **Processing latency:** Similar to queues, streaming adds asynchronous processing latency. * **Setup complexity:** Most complex integration method to set up and operate. ## Responding back to chat From 88d56db4fdbd262ca761cf7b2f2e1c5ca7558dae Mon Sep 17 00:00:00 2001 From: Steven Lindsay Date: Mon, 15 Dec 2025 17:23:35 +0000 Subject: [PATCH 06/33] Rework benefits and considerations sections --- .../guides/chat/external-integrations.mdx | 143 ++++-------------- 1 file changed, 31 insertions(+), 112 deletions(-) diff --git a/src/pages/docs/guides/chat/external-integrations.mdx b/src/pages/docs/guides/chat/external-integrations.mdx index d805e7372e..822daffa5c 100644 --- a/src/pages/docs/guides/chat/external-integrations.mdx +++ b/src/pages/docs/guides/chat/external-integrations.mdx @@ -219,22 +219,21 @@ async function processMessage(roomName, text, action) { ## Using a webhook -Ably can forward messages to your external system via a webhook. This is the simplest to set up and offers the lowest latency. This section covers the simple HTTP endpoint webhook, but the same principles apply to other webhook integrations such as AWS Lambda, Azure Function, Google Cloud Function, and others. +Ably can forward messages to your external system via a webhook. This is the simplest integration method to set up and works well for most use cases. This section covers HTTP endpoint webhooks, but the same principles apply to other webhook integrations such as AWS Lambda, Azure Function, Google Cloud Function, and others. Read the guide on [outbound webhooks](/docs/platform/integrations/webhooks) for more details on how to set up a webhook with Ably for the platform of your choice. -**Benefits:** -* Easiest to set up and understand -* No infrastructure to manage with serverless functions -* Direct HTTP invocation with automatic retries -* Native support for major cloud providers +Benefits: +- Simplest integration method to implement and get started quickly. +- Works with any HTTP endpoint or serverless function. +- Automatic retry handling with configurable retry windows. +- No additional infrastructure required beyond your webhook endpoint or function. -**You need to consider:** -* **Cold start latency:** Serverless functions may have cold start delays. Consider keeping functions warm for latency-sensitive use cases. -* **Limited retry window:** Ably will retry delivering the message to your webhook, but only for a short period. Monitor the [`[meta]log` channel](/docs/platform/errors#meta) for delivery failures. -* **Scale limits:** Webhook scale depends on your function's concurrency configuration. Ensure your function can handle expected message volumes. -* **[At-least-once delivery](/docs/platform/architecture/idempotency#protocol-support-for-exactly-once-delivery):** Deduplication can be done by checking `serial` and `version.serial`. -* **Ordering:** Messages can arrive out-of-order. Use `serial` and `version.serial` properties to order them correctly if needed. +You need to consider: +- Ably retries failed webhook deliveries, but only for a limited retry window. Monitor the [`[meta]log` channel](/docs/platform/errors#meta) for delivery failures. +- Messages can arrive out-of-order. Use `serial` and `version.serial` properties to order them correctly if needed. +- Failed webhook calls that exceed the retry window will lead to message loss. Implement monitoring and alerting to detect failures early. +- [At-least-once delivery](/docs/platform/architecture/idempotency#protocol-support-for-exactly-once-delivery) means you need to handle duplicate messages. Deduplication can be done by tracking `serial` and `version.serial`. ## Using an Ably queue @@ -242,61 +241,50 @@ Ably can forward messages from chat room channels to an [Ably Queue](/docs/platf Ably ensures that each message is delivered to only one consumer even if multiple consumers are connected. -**Benefits:** -* At-least-once delivery with dead letter queue support. -* No message loss if your consumers temporarily disconnect. -* Scale consumers independently by running multiple instances. -* Use multiple queues for different channels, filtering via regex on channel name. -* Ably manages the queue infrastructure for you. +Benefits of using an Ably queue: +- You can consume it from your servers, meaning overall this is fault-tolerant. Ably takes care of the complexity of maintaining a queue. +- You can use multiple queues and configure which channels go to which queue via regex filters on the channel name. +- Fault-tolerant: if your systems suffer any temporary downtime, you will not miss messages, up to the queue max size. There is a dead letter queue to handle the situation where messages are dropped from the Ably Queue. -**You need to consider:** -* **Queue limits:** Each message has a time-to-live (TTL) in the queue. The default and maximum is 60 minutes. -* **Consumer scaling:** During peak times you may need to scale up your consumers to avoid going over the maximum queue length allowed. -* **Dead letter queue:** Oldest messages are dropped if the maximum queue length is exceeded. Always consume messages from the [dead letter queue](/docs/platform/integrations/queues#deadletter) to monitor errors. -* **Consumer infrastructure:** You need to manage and maintain your consumer servers or containers that process messages from the queue. +You need to consider: +- During peak times you may need to scale up your consumers to avoid overloading the queue past the maximum queue length allowed. +- Each message has a time-to-live in the queue. The default and maximum is 60 minutes. +- Oldest messages are dropped if the maximum queue length is exceeded. Check the [dead letter queue](/docs/platform/integrations/queues#deadletter) to see if this is happening. +- Always consume messages from the [dead letter queue](/docs/platform/integrations/queues#deadletter) to monitor errors. ## Using outbound streaming Ably can stream messages directly to your own queueing or streaming service: Kafka, Kinesis, AMQP, SQS, Pulsar. Read the guide on [outbound streaming](/docs/platform/integrations/streaming) for more details on how to set up the streaming integration with Ably for the service of your choice. -**Benefits:** -* Massive scale (millions of messages/second) -* Multiple consumers can process the same stream -* Integration with existing data infrastructure -* Use your existing queue system to process messages from Ably +Benefits: +- Use your existing queue system to process messages from Ably. +- You control your own queue system, so you have full control over message ingestion in terms of retry strategies, retention policies, queue lengths, and so on. -**You need to consider:** -* **Infrastructure complexity:** You need to maintain and be responsible for a reliable streaming system. If you don't already have such a system, it increases complexity. -* **Consistency:** If your streaming system is not reachable, messages may be lost. Errors can be seen in the [`[meta]log` channel](/docs/platform/errors#meta). -* **Setup complexity:** Most complex integration method to set up and operate. +You need to consider: +- You need to maintain and be responsible for a reliable streaming system. If you don't already have such a system, it increases complexity on your end. +- Consistency. If your streaming system is not reachable, messages may be lost. Errors can be seen in the [`[meta]log` channel](/docs/platform/errors#meta). ## Responding back to chat After processing messages through your external system, you will typically want to publish responses back to the chat room. This could be answers from an AI assistant, translated text, enriched content, or status updates. -To avoid triggering the integration rule on the response message, Use Ably's [skip integrations](/docs/platform/integrations/skip-integrations) feature to publish messages that bypass integration rules. +To avoid triggering the integration rule on the response message, use Ably's [skip integrations](/docs/platform/integrations/skip-integrations) feature to publish messages that bypass integration rules. **Note:** Your API key or token must have the [`privileged-headers`](/docs/auth/capabilities#capability-operations) capability to apply the skip integration flag. ```javascript -const Ably = require('ably'); - -async function sendResponseToChat(envelopeData, responseText) { +async function sendResponseToChat(channelName, responseText) { // Extract the room name from the channel name - const channelName = envelopeData.channel; // e.g., "chat:support::$chat" const roomName = channelName.replace('::$chat', ''); // e.g., "chat:support" - - // Initialize Chat client and get the room - const chatClient = new Ably.Chat(realtime); const room = await chatClient.rooms.get(roomName); - // Send response message skipping all integration rules + // Send response message skipping some or all integration rules await room.messages.send({ text: responseText }, { extras: { - privileged: {' - skipRule: [ruleIdToSkip] // To skip all rules use '*' instead of an array + privileged: { + skipRule: '*' // Skip all integration rules } } }); @@ -306,75 +294,6 @@ async function sendResponseToChat(envelopeData, responseText) { The rule ID is available in the integration webhook envelope's `ruleId` field, or you can find it in your Ably [dashboard](https://ably.com/dashboard) under Integrations. -## Best practices for production - -When building production-ready integrations, follow these best practices: - -### Error handling and resilience - -* **Retry logic:** Implement exponential backoff for failed external API calls to handle transient failures gracefully -* **Circuit breakers:** Prevent cascading failures by temporarily stopping requests to failing external services -* **Timeouts:** Set appropriate timeouts for external API calls to avoid indefinite blocking -* **Graceful degradation:** Design your system to handle external service failures without breaking the entire integration -* See [Ably's architecture documentation](/docs/platform/architecture/fault-tolerance) for resilience patterns - -### Idempotency - -* **Message deduplication:** Track processed message IDs to safely handle Ably's [at-least-once delivery](/docs/platform/architecture/idempotency#protocol-support-for-exactly-once-delivery) -* **Idempotent operations:** Design your external system operations to be safely retryable -* **Cleanup strategy:** Implement cleanup for tracked message IDs to prevent memory leaks (LRU cache, TTL-based cleanup) - -### Rate limiting and caching - -* **Cache responses:** Cache frequent external API responses to reduce latency and costs -* **Rate limits:** Respect external service rate limits with throttling and queuing -* **Batching:** Consider batching multiple operations to reduce external API calls -* **CDN integration:** Use CDNs for static content enrichment (images, metadata) - -### Security - -* **Webhook signatures:** Always validate webhook signatures using Ably's provided signature headers -* **Token authentication:** Use short-lived [JWT tokens](/docs/auth/token#jwt) with minimal capabilities for client connections -* **API key security:** Never embed API keys in client code; use server-side endpoints to generate tokens -* **TLS/HTTPS:** Ensure all connections use TLS encryption -* **Secret management:** Store API keys and secrets in secure management systems (AWS Secrets Manager, Azure Key Vault, etc.) -* See [Ably's authentication documentation](/docs/auth) for security best practices - -### Monitoring and observability - -* **[Meta channel](/docs/metadata-stats/metadata/subscribe) monitoring:** Subscribe to `[meta]log:integration` to track integration errors -* **Dead letter queues:** Monitor [dead letter queues](/docs/platform/integrations/queues#deadletter) if using Ably Queues -* **Metrics tracking:** Monitor error rates, latency, throughput, and external service response times -* **Alerting:** Set up alerts for integration failures, queue depth issues, and performance degradation -* **Logging:** Implement structured logging with correlation IDs for debugging - -## Production checklist - -Before deploying your integration to production, verify: - -- [ ] Integration rule filters correctly match target channels -- [ ] External system authenticated and authorized properly -- [ ] Response messages configured to avoid infinite loops (separate channels or privileged skipRule) -- [ ] API key has `privileged-headers` capability if using skipRule feature -- [ ] Error handling and logging implemented -- [ ] Retry logic and exponential backoff in place -- [ ] Monitoring and alerting configured - - [ ] Subscribe to `[meta]log` channel - - [ ] Track error rates and latency - - [ ] Monitor queue depths (if using queues) - - [ ] Alert on dead letter queue messages -- [ ] Load tested at expected scale (+ 50% buffer) -- [ ] Cost estimation validated based on message volume -- [ ] Security review completed - - [ ] No API keys embedded in client code - - [ ] TLS/HTTPS enabled for all connections - - [ ] Webhook signatures validated - - [ ] Secrets stored in secure management system -- [ ] Idempotency handling implemented -- [ ] Documentation for your team on how integration works -- [ ] Rollback plan prepared in case of issues -- [ ] Test in staging environment before production - ## Next steps Explore related documentation to deepen your integration knowledge: From abdb3512de3f7a9de7a7db91ba1ab504e23a3d6f Mon Sep 17 00:00:00 2001 From: Steven Lindsay Date: Mon, 15 Dec 2025 17:48:14 +0000 Subject: [PATCH 07/33] correcting enveloped payload --- src/pages/docs/guides/chat/external-integrations.mdx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/pages/docs/guides/chat/external-integrations.mdx b/src/pages/docs/guides/chat/external-integrations.mdx index 822daffa5c..daf7025cff 100644 --- a/src/pages/docs/guides/chat/external-integrations.mdx +++ b/src/pages/docs/guides/chat/external-integrations.mdx @@ -131,10 +131,12 @@ With enveloping enabled (the default), Ably wraps messages in additional metadat "ruleId": "integration-rule-id", "messages": [ { - "id": "message-id", - "name": "event-name", - "connectionId": "connection-id", + "id": "oWSRIqvv2d:0:0", + "clientId": "some-client-id", + "name": "chat.message", "timestamp": 1234567890, + "serial": "01765820788939-000@108wgxjJwBwuAB37648671:000", + "action": 0, "data": { "text": "Message content", "metadata": {} From b979185fec676f0d1398af87e54ad8f03a660c8e Mon Sep 17 00:00:00 2001 From: Steven Lindsay Date: Mon, 15 Dec 2025 17:53:58 +0000 Subject: [PATCH 08/33] wip --- src/pages/docs/guides/chat/external-integrations.mdx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/pages/docs/guides/chat/external-integrations.mdx b/src/pages/docs/guides/chat/external-integrations.mdx index daf7025cff..4e0f4d2815 100644 --- a/src/pages/docs/guides/chat/external-integrations.mdx +++ b/src/pages/docs/guides/chat/external-integrations.mdx @@ -185,19 +185,19 @@ async function processIntegrationPayload(envelopeData) { // Extract text content const text = msg.data?.text; - // Extract headers (used for routing and control) + // Extract headers if needed const headers = msg.extras?.headers || {}; const action = headers['x-integration-action']; - // Extract metadata (custom structured data) + // Extract metadata if needed const metadata = msg.data?.metadata || {}; - // Process each message - await processMessage(roomName, text, action); + // Process each message based on action + await processServiceRequest(roomName, text, action); } } -async function processMessage(roomName, text, action) { +async function processServiceRequest(roomName, text, action) { switch (action) { case 'weather': await handleWeatherRequest(text, roomName); @@ -227,7 +227,6 @@ Read the guide on [outbound webhooks](/docs/platform/integrations/webhooks) for Benefits: - Simplest integration method to implement and get started quickly. -- Works with any HTTP endpoint or serverless function. - Automatic retry handling with configurable retry windows. - No additional infrastructure required beyond your webhook endpoint or function. From 00f9857f9acb72fdc6dd1cb1e59c8b087dc5784a Mon Sep 17 00:00:00 2001 From: Steven Lindsay Date: Mon, 15 Dec 2025 18:00:58 +0000 Subject: [PATCH 09/33] Remove mention of moderation systems --- src/pages/docs/guides/chat/external-integrations.mdx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/pages/docs/guides/chat/external-integrations.mdx b/src/pages/docs/guides/chat/external-integrations.mdx index 4e0f4d2815..eb08e4f7ec 100644 --- a/src/pages/docs/guides/chat/external-integrations.mdx +++ b/src/pages/docs/guides/chat/external-integrations.mdx @@ -12,15 +12,13 @@ This guide explains how to architect integrations that process chat messages thr Integrating external systems with Ably Chat enables you to extend your chat application beyond simple message delivery. Common integration use cases include: -* **AI-powered assistants:** Process commands or questions through language models and respond with helpful information. +* **AI-powered assistance:** Process commands or questions through language models and respond with helpful information. * **Real-time translation:** Automatically translate messages for multilingual chat rooms. * **Notifications:** Detect and process mentions, or other notifications. * **Analytics and monitoring:** Analyze sentiment, track engagement metrics, or identify trending topics. * **Workflow automation:** Trigger external business processes based on chat activity. * **Command processing:** Handle slash commands that invoke server-side logic. -Unlike [chat moderation](/docs/chat/moderation), which filters or blocks content before or after publication, integrations focus on processing messages and responding with new content or triggering external actions. Also unlike [exporting chat data](/docs/guides/chat/export-chat), which stores all messages for long-term retention, integrations process specific messages matched by integration rules. - ## Why Ably for external integrations? Ably Chat integrations are built on Ably's proven platform architecture, designed to handle integration workloads at any scale: From 0dfe992b751abc931f1c1991414b5436d3a1a99c Mon Sep 17 00:00:00 2001 From: Steven Lindsay Date: Mon, 15 Dec 2025 18:04:11 +0000 Subject: [PATCH 10/33] add anchor links --- .../guides/chat/external-integrations.mdx | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/pages/docs/guides/chat/external-integrations.mdx b/src/pages/docs/guides/chat/external-integrations.mdx index eb08e4f7ec..ae5def335d 100644 --- a/src/pages/docs/guides/chat/external-integrations.mdx +++ b/src/pages/docs/guides/chat/external-integrations.mdx @@ -8,7 +8,7 @@ Ably Chat is designed for extensibility. Whether you need to process messages wi This guide explains how to architect integrations that process chat messages through external systems and respond back to the chat in real-time. Before diving into the technical implementation, it's important to understand your architectural goals and the role your external system will play. -## Why integrate external systems? +## Why integrate external systems? Integrating external systems with Ably Chat enables you to extend your chat application beyond simple message delivery. Common integration use cases include: @@ -19,7 +19,7 @@ Integrating external systems with Ably Chat enables you to extend your chat appl * **Workflow automation:** Trigger external business processes based on chat activity. * **Command processing:** Handle slash commands that invoke server-side logic. -## Why Ably for external integrations? +## Why Ably for external integrations? Ably Chat integrations are built on Ably's proven platform architecture, designed to handle integration workloads at any scale: @@ -48,7 +48,7 @@ With your strategy in mind, choose the technical approach that fits your needs: 2. Using [Ably queues](#ably-queue). 3. Using [outbound streaming](#outbound-streaming). Stream to your own [Kafka](/docs/platform/integrations/streaming/kafka), [Kinesis](/docs/platform/integrations/streaming/kinesis), and others. -## Understanding integrations +## Understanding integrations Ably Chat rooms are built on Ably Pub/Sub channels, which means you can leverage Ably's integration capabilities to forward messages to external systems for processing: @@ -57,7 +57,7 @@ Ably Chat rooms are built on Ably Pub/Sub channels, which means you can leverage * **Integration rules:** Configure rules that filter which messages trigger integrations based on channel name patterns and event types. * **Bidirectional flow:** Integrations allow you to export messages to external systems, process them, and respond back to chat rooms in real-time. -### Integration flow +### Integration flow The typical integration flow works as follows: @@ -75,7 +75,7 @@ The typical integration flow works as follows: Integrations allow you to filter which Chat rooms are forwarded to your external system using a regular expression on the room name. This is a simple way to reduce the volume of messages you need to process by only receiving messages from the chat rooms you are interested in. -### Setting up integration rules +### Setting up integration rules When configuring an integration rule in your Ably dashboard: @@ -83,7 +83,7 @@ When configuring an integration rule in your Ably dashboard: * **Event type:** Use `channel.message` for all integration types. This forwards all chat messages published to relevant rooms and excludes presence messages and channel lifecycle messages * **Enveloped messages:** Enable this to receive all metadata about the message, including the `serial`, `version`, and `extras` (which include the [`headers`](/docs/chat/rooms/messages#structure) of a chat message) -### Understanding channel names and room names +### Understanding channel names and room names Chat rooms are underpinned by Ably Pub/Sub channels, but with a specific suffix (`::$chat`) added to form the full channel name. When using the Chat SDK to create or get a room, this is done automatically for you, you do not need to include the suffix yourself. @@ -115,7 +115,7 @@ await generalRoom.messages.send({ text: 'Hi' }); // Won't trigger if filter is ^ When your integration receives messages, you need to decode them and extract the relevant information. -### Understanding enveloped messages +### Understanding enveloped messages With enveloping enabled (the default), Ably wraps messages in additional metadata like so: @@ -150,7 +150,7 @@ With enveloping enabled (the default), Ably wraps messages in additional metadat ``` -### Extracting the room name +### Extracting the room name Your integration receives the full channel name including the `::$chat` suffix. To send responses back to the same room, extract the room name by removing the suffix: @@ -165,7 +165,7 @@ function processIntegrationPayload(envelopeData) { ``` -### Extracting message data +### Extracting message data The Ably SDK provides methods to decode messages from the enveloped payload. From there, you can extract the text content, headers, and metadata and process them as needed. @@ -293,7 +293,7 @@ async function sendResponseToChat(channelName, responseText) { The rule ID is available in the integration webhook envelope's `ruleId` field, or you can find it in your Ably [dashboard](https://ably.com/dashboard) under Integrations. -## Next steps +## Next steps Explore related documentation to deepen your integration knowledge: From 9f921eb2e14e7e2cb2769c006d8811a81a289dd3 Mon Sep 17 00:00:00 2001 From: Steven Lindsay Date: Mon, 15 Dec 2025 18:10:17 +0000 Subject: [PATCH 11/33] wip --- src/pages/docs/guides/chat/external-integrations.mdx | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/pages/docs/guides/chat/external-integrations.mdx b/src/pages/docs/guides/chat/external-integrations.mdx index ae5def335d..1e8d14744d 100644 --- a/src/pages/docs/guides/chat/external-integrations.mdx +++ b/src/pages/docs/guides/chat/external-integrations.mdx @@ -42,7 +42,7 @@ Consider the following when integrating external systems with Ably Chat: ## Implementation options -With your strategy in mind, choose the technical approach that fits your needs: +You can configure many different types of integrations with Ably Chat. The three methods commonly used, and covered in this guide, are: 1. Using [outbound webhooks](#webhook). [HTTP endpoint](/docs/platform/integrations/webhooks/generic), [AWS Lambda](/docs/platform/integrations/webhooks/lambda), and others. 2. Using [Ably queues](#ably-queue). @@ -98,15 +98,13 @@ Integration rules match against the full channel name, but you don't need to inc const supportRoom = await chatClient.rooms.get('chat:support'); // Underlying channel: chat:support::$chat -const groupRoom = await chatClient.rooms.get('chat:group:123'); -// Underlying channel: chat:group:123::$chat - // Messages sent to these rooms will trigger an integration // if your rule's channel filter is: ^chat:support.* await supportRoom.messages.send({ text: 'Need help' }); // Messages sent to other channel patterns will NOT trigger the integration const generalRoom = await chatClient.rooms.get('chat:general'); +// Underlying channel: chat:general::$chat await generalRoom.messages.send({ text: 'Hi' }); // Won't trigger if filter is ^chat:support.* ``` From 8e27f4ea5b7c89e30c5a1871e709e2cc7c41da71 Mon Sep 17 00:00:00 2001 From: Steven Lindsay Date: Tue, 16 Dec 2025 15:18:57 +0000 Subject: [PATCH 12/33] create feature doc for integration uses --- .../docs/chat/rooms/message-extraction.mdx | 383 ++++++++++++++++++ 1 file changed, 383 insertions(+) create mode 100644 src/pages/docs/chat/rooms/message-extraction.mdx diff --git a/src/pages/docs/chat/rooms/message-extraction.mdx b/src/pages/docs/chat/rooms/message-extraction.mdx new file mode 100644 index 0000000000..ca25560963 --- /dev/null +++ b/src/pages/docs/chat/rooms/message-extraction.mdx @@ -0,0 +1,383 @@ +--- +title: "Extract messages via integrations" +meta_description: "Extract chat messages from Ably Chat using integrations for external processing, storage, or analysis." +meta_keywords: "chat integrations, message extraction, webhooks, streaming, queues, message processing, chat data" +--- + +Extract chat messages from Ably Chat rooms to external systems using Ably's integration capabilities. This enables you to process, store, or analyze messages outside of Ably Chat while maintaining realtime message delivery to chat participants. + +Chat rooms are built on Ably Pub/Sub channels, allowing you to leverage the full range of Ably's [platform integrations](/docs/platform/integrations) to forward messages to external systems. + +## Integration methods + +Ably provides three primary methods for extracting chat messages: + +1. **[Webhooks](#webhooks)** - Forward messages to HTTP endpoints, AWS Lambda, Azure Functions, and more +2. **[Queues](#queues)** - Route messages to Ably-managed queues for consumption by your services +3. **[Streaming](#streaming)** - Stream messages to external systems like Kafka, Kinesis, or SQS + +Each method offers different trade-offs in terms of simplicity, reliability, and infrastructure requirements. + +## Filtering rooms + +Control which chat rooms trigger integrations using channel name filters. + +### Setting up filters + +When configuring an integration rule in your Ably dashboard: + +* **Channel filter**: Use a regular expression to match room names. For example, `^support:.*` matches all rooms starting with `support:` +* **Event type**: Use `channel.message` to forward chat messages and exclude presence events +* **Enveloped messages**: Enable to receive full message metadata including `serial`, `version`, and `headers` + +**Note**: Chat rooms use the `::$chat` suffix on channel names internally. Integration filters match against the full channel name, but you don't need to include the suffix in your filter pattern. + +### Example filter configuration + + +```javascript +// Room name: support:ticket-123 +// Internal channel: support:ticket-123::$chat +// Filter pattern: ^support:.* +// Result: Messages from this room will be forwarded + +const supportRoom = await chatClient.rooms.get('support:ticket-123'); +await supportRoom.messages.send({ text: 'Help needed' }); // Will trigger integration +``` + + +## Decoding messages + +Messages received through integrations are encoded as Ably Pub/Sub messages and need to be decoded into Chat messages. Full details on the mapping are available in the [Chat integrations](/docs/chat/integrations) documentation. + +### Understanding enveloped messages + +With enveloping enabled (recommended), messages arrive wrapped in metadata: + + +```javascript +{ + "source": "channel.message", + "appId": "your-app-id", + "channel": "support:ticket-123::$chat", + "ruleId": "integration-rule-id", + "messages": [ + { + "id": "unique-message-id", + "clientId": "user-123", + "name": "chat.message", + "timestamp": 1234567890, + "serial": "01765820788939-000@108wgxjJwBwuAB37648671:000", + "action": 0, + "data": { + "text": "Message content", + "metadata": {} + }, + "extras": { + "headers": {} + } + } + ] +} +``` + + +### Extracting room name + +Remove the `::$chat` suffix to get the room name: + + +```javascript +function extractRoomName(channelName) { + // channelName: "support:ticket-123::$chat" + return channelName.replace('::$chat', ''); // Returns: "support:ticket-123" +} +``` + + +### Decoding message data + +Use the Ably SDK to decode messages from the envelope: + + +```javascript +const Ably = require('ably'); + +function decodeMessages(envelopeData) { + const decodedMessages = Ably.Realtime.Message.fromEncodedArray(envelopeData.messages); + + return decodedMessages.map(msg => ({ + serial: msg.serial, + text: msg.data?.text, + metadata: msg.data?.metadata || {}, + headers: msg.extras?.headers || {}, + clientId: msg.clientId, + timestamp: msg.timestamp, + action: msg.action, + versionSerial: msg.version?.serial || msg.serial + })); +} +``` + + +## Message versioning + +Chat messages have a versioning system for updates and deletes: + +* **`serial`**: Unique identifier for the original message +* **`version.serial`**: Identifier for the specific message version +* **`action`**: Indicates message type (create, update, delete, or summary for reactions) + +### Handling message versions + + +```javascript +function processMessage(message) { + // Discard reaction summaries if not needed + if (message.action === 'message.summary') { + return; + } + + // Process based on action + switch (message.action) { + case 'message.created': + handleNewMessage(message); + break; + case 'message.updated': + handleMessageUpdate(message); + break; + case 'message.deleted': + handleMessageDelete(message); + break; + } +} +``` + + +### Version ordering + +Version serials are lexicographically ordered. A higher `version.serial` indicates a newer version: + + +```javascript +function isNewerVersion(existingVersion, newVersion) { + return newVersion > existingVersion; +} + +// Only process if this is a newer version +if (isNewerVersion(stored.version.serial, incoming.version.serial)) { + updateMessage(incoming); +} +``` + + +Read more about [message versioning and sorting](/docs/chat/rooms/messages#ordering-update-delete) in the messages documentation. + +## Using webhooks + +Forward messages to HTTP endpoints or serverless functions. + +### Setup + +Configure a webhook integration in your Ably dashboard pointing to your endpoint. See [outbound webhooks](/docs/platform/integrations/webhooks) for detailed setup. + +### Considerations + +* **Retry window**: Limited automatic retry period for failed deliveries +* **Ordering**: Messages may arrive out-of-order; use `serial` and `version.serial` to sort +* **Deduplication**: Handle potential duplicates using message serials +* **Monitoring**: Use [`[meta]log` channel](/docs/platform/errors#meta) to detect failures + +### Example webhook handler + + +```javascript +async function handleWebhook(req, res) { + const envelope = req.body; + const roomName = extractRoomName(envelope.channel); + const messages = decodeMessages(envelope); + + for (const message of messages) { + await processMessage(roomName, message); + } + + res.status(200).send('OK'); +} +``` + + +## Using queues + +Route messages to Ably-managed queues for consumption. + +### Setup + +Configure an Ably queue integration in your dashboard. See [Ably queues](/docs/platform/integrations/queues) for setup details. + +### Benefits + +* Fault-tolerant message delivery +* Messages persist during consumer downtime (up to queue limits) +* Dead letter queue for dropped messages +* At-least-once delivery guarantee + +### Considerations + +* **Queue limits**: Monitor queue length during peak times +* **TTL**: Messages expire after 60 minutes (default/max) +* **Dead letter queue**: Always consume to monitor for dropped messages +* **Scaling**: Scale consumers to match message volume + +### Example queue consumer + + +```javascript +async function consumeQueue(queueName) { + const queue = await client.queues.get(queueName); + + queue.subscribe(async (message) => { + const roomName = extractRoomName(message.channel); + const decoded = decodeMessages(message); + + await processMessages(roomName, decoded); + + // Acknowledge message + await message.ack(); + }); +} +``` + + +## Using streaming + +Stream messages to external systems like Kafka or Kinesis. + +### Setup + +Configure streaming to your target system. See [outbound streaming](/docs/platform/integrations/streaming) for setup details. + +### Benefits + +* Leverage existing streaming infrastructure +* Full control over retention and processing +* Massive scale capabilities + +### Considerations + +* **Infrastructure**: You manage and maintain the streaming system +* **Reliability**: Messages lost if system unavailable +* **Complexity**: Higher operational overhead + +### Example Kafka consumer + + +```javascript +const { Kafka } = require('kafkajs'); + +async function consumeFromKafka() { + const consumer = kafka.consumer({ groupId: 'chat-processor' }); + await consumer.connect(); + await consumer.subscribe({ topic: 'ably-chat-messages' }); + + await consumer.run({ + eachMessage: async ({ message }) => { + const envelope = JSON.parse(message.value); + const roomName = extractRoomName(envelope.channel); + const decoded = decodeMessages(envelope); + + await processMessages(roomName, decoded); + } + }); +} +``` + + +## Handling message reactions + +Message reactions are delivered as separate events with `action: 'message.summary'`. + +### Reaction summary structure + + +```javascript +{ + "action": "message.summary", + "serial": "original-message-serial", + "annotations": { + "summary": { + "reaction:unique.v1": { + "👍": { "count": 5 }, + "❤️": { "count": 3 } + } + } + } +} +``` + + +### Processing reactions + + +```javascript +function processMessage(message) { + if (message.action === 'message.summary') { + // Handle reaction summary + const reactions = message.annotations?.summary?.['reaction:unique.v1'] || {}; + updateReactionCounts(message.serial, reactions); + return; + } + + // Handle regular message + saveMessage(message); +} +``` + + +Read more about [message reactions](/docs/chat/rooms/message-reactions) and the [reactions annotation mapping](/docs/chat/integrations#how-to-handle-message-reactions). + +## Error handling and monitoring + +### Monitor integration failures + +Subscribe to the [`[meta]log` channel](/docs/platform/errors#meta) to detect integration failures: + + +```javascript +const metaChannel = client.channels.get('[meta]log:integration-rule-id'); + +metaChannel.subscribe((message) => { + if (message.data.error) { + console.error('Integration error:', message.data); + alertOnFailure(message.data); + } +}); +``` + + +### Dead letter queues + +For queue integrations, monitor the dead letter queue: + + +```javascript +async function monitorDeadLetterQueue(appID) { + const dlq = await client.queues.get(`${appID}:deadletter`); + + dlq.subscribe(async (message) => { + console.error('Message dropped from queue:', message); + await alertAndRetry(message); + }); +} +``` + + +**Note:** Only one dead letter queue exists per Ably application, shared across all queues. + +## Related documentation + +* [Chat integrations](/docs/chat/integrations) - Technical reference for message structure mapping +* [Platform integrations](/docs/platform/integrations) - Complete integration setup guides +* [Message structure](/docs/chat/rooms/messages#structure) - Chat message format details +* [Chat history](/docs/chat/rooms/history) - Retrieve historical messages via API +* [Export chat data guide](/docs/guides/chat/export-chat) - Guide for long-term storage use cases +* [External integrations guide](/docs/guides/chat/external-integrations) - Guide for processing and responding to messages From cceefe7f9867875e131ad635b6e17b7b4d50c804 Mon Sep 17 00:00:00 2001 From: Steven Lindsay Date: Tue, 16 Dec 2025 15:39:29 +0000 Subject: [PATCH 13/33] Rework external integration guide so it's more condensed and focused more outside of specific ably features --- src/data/nav/chat.ts | 4 + .../docs/chat/rooms/message-extraction.mdx | 247 ++++++++--- .../guides/chat/external-integrations.mdx | 399 ++++++++++-------- 3 files changed, 413 insertions(+), 237 deletions(-) diff --git a/src/data/nav/chat.ts b/src/data/nav/chat.ts index 64164729b1..f2e3d5f4a4 100644 --- a/src/data/nav/chat.ts +++ b/src/data/nav/chat.ts @@ -112,6 +112,10 @@ export default { name: 'Message replies', link: '/docs/chat/rooms/replies', }, + { + name: 'Message Extraction', + link: '/docs/chat/rooms/message-extraction', + }, ], }, { diff --git a/src/pages/docs/chat/rooms/message-extraction.mdx b/src/pages/docs/chat/rooms/message-extraction.mdx index ca25560963..813abea86f 100644 --- a/src/pages/docs/chat/rooms/message-extraction.mdx +++ b/src/pages/docs/chat/rooms/message-extraction.mdx @@ -12,9 +12,9 @@ Chat rooms are built on Ably Pub/Sub channels, allowing you to leverage the full Ably provides three primary methods for extracting chat messages: -1. **[Webhooks](#webhooks)** - Forward messages to HTTP endpoints, AWS Lambda, Azure Functions, and more -2. **[Queues](#queues)** - Route messages to Ably-managed queues for consumption by your services -3. **[Streaming](#streaming)** - Stream messages to external systems like Kafka, Kinesis, or SQS +1. **[Webhooks](#webhooks)** - Forward messages to [HTTP endpoints](/docs/platform/integrations/webhooks/generic), [AWS Lambda](/docs/platform/integrations/webhooks/lambda), [Google Cloud Functions](/docs/platform/integrations/webhooks/gcp-function), [Cloudflare Workers](/docs/platform/integrations/webhooks/cloudflare), and more +2. **[Queues](#queues)** - Route messages to [Ably-managed queues](/docs/platform/integrations/queues) for consumption by your services +3. **[Streaming](#streaming)** - Stream messages to external systems like [Kafka](/docs/platform/integrations/streaming/kafka), [Kinesis](/docs/platform/integrations/streaming/kinesis), [SQS](/docs/platform/integrations/streaming/sqs), [AMQP](/docs/platform/integrations/streaming/amqp), or [Pulsar](/docs/platform/integrations/streaming/pulsar) Each method offers different trade-offs in terms of simplicity, reliability, and infrastructure requirements. @@ -175,18 +175,24 @@ Read more about [message versioning and sorting](/docs/chat/rooms/messages#order ## Using webhooks -Forward messages to HTTP endpoints or serverless functions. +[Outbound webhooks](/docs/platform/integrations/webhooks) enable you to forward messages to HTTP endpoints or serverless functions. ### Setup -Configure a webhook integration in your Ably dashboard pointing to your endpoint. See [outbound webhooks](/docs/platform/integrations/webhooks) for detailed setup. +Configure a webhook integration in your Ably dashboard pointing to your endpoint. See the following for platform-specific setup guides: + +* [Generic HTTP webhook](/docs/platform/integrations/webhooks/generic) - Forward to any HTTP/HTTPS endpoint +* [AWS Lambda](/docs/platform/integrations/webhooks/lambda) - Trigger Lambda functions +* [Google Cloud Functions](/docs/platform/integrations/webhooks/gcp-function) - Invoke GCP functions +* [Cloudflare Workers](/docs/platform/integrations/webhooks/cloudflare) - Trigger Workers +* [General webhook documentation](/docs/platform/integrations/webhooks) - Overview and other platforms ### Considerations -* **Retry window**: Limited automatic retry period for failed deliveries +* **Retry behavior**: For Lambda, AWS automatically retries failed invocations up to two times with delays (1 minute, then 2 minutes). For generic HTTP webhooks, Ably retries for a limited window * **Ordering**: Messages may arrive out-of-order; use `serial` and `version.serial` to sort -* **Deduplication**: Handle potential duplicates using message serials -* **Monitoring**: Use [`[meta]log` channel](/docs/platform/errors#meta) to detect failures +* **Deduplication**: Handle potential duplicates using message serials - webhooks provide at-least-once delivery +* **Monitoring**: Use [`[meta]log` channel](/docs/metadata-stats/metadata/subscribe#log) to detect failures (see [monitoring section](#monitoring-performance)) ### Example webhook handler @@ -208,11 +214,17 @@ async function handleWebhook(req, res) { ## Using queues -Route messages to Ably-managed queues for consumption. +[Ably Queues](/docs/platform/integrations/queues) are traditional message queues that enable you to consume, process, or store chat messages from your servers. ### Setup -Configure an Ably queue integration in your dashboard. See [Ably queues](/docs/platform/integrations/queues) for setup details. +Configure an Ably queue integration in your dashboard: + +1. [Provision a queue](/docs/platform/integrations/queues#provision) with your desired region, TTL, and max length settings +2. [Create a queue rule](/docs/platform/integrations/queues#config) to route chat messages to the queue +3. Consume messages using [AMQP](/docs/platform/integrations/queues#amqp) or [STOMP](/docs/platform/integrations/queues#stomp) protocols + +See [Ably queues](/docs/platform/integrations/queues) for complete setup details. ### Benefits @@ -223,38 +235,104 @@ Configure an Ably queue integration in your dashboard. See [Ably queues](/docs/p ### Considerations -* **Queue limits**: Monitor queue length during peak times -* **TTL**: Messages expire after 60 minutes (default/max) -* **Dead letter queue**: Always consume to monitor for dropped messages -* **Scaling**: Scale consumers to match message volume +* **Queue limits**: Default maximum is 10,000 messages. Monitor queue length during peak times to avoid reaching capacity +* **TTL**: Messages expire after 60 minutes (default/max) if not consumed +* **Dead letter queue**: Automatically provisioned. Always consume to monitor for dropped or expired messages +* **Throughput**: Multi-tenanted queues support up to 200 messages/second per account. For higher volumes, consider [dedicated queues or streaming](/docs/platform/integrations/queues#scalability) +* **Ordering**: Messages maintain order per channel with a single consumer. Multiple consumers or multi-channel messages may affect ordering + +### Example queue consumer (AMQP) + + +```javascript +const amqp = require('amqplib'); + +// Queue name format: APPID:queue-name +const queueName = 'your-app-id:chat-messages'; +// Avoid hardcoding credentials in production +const url = 'amqps://APPID.KEYID:SECRET@us-east-1-a-queue.ably.io/shared'; + +amqp.connect(url, (err, conn) => { + if (err) { return console.error(err); } + + conn.createChannel((err, ch) => { + if (err) { return console.error(err); } + + // Subscribe to the queue + ch.consume(queueName, (item) => { + const envelope = JSON.parse(item.content); + const roomName = extractRoomName(envelope.channel); + + // Decode messages using Ably SDK + const messages = Ably.Realtime.Message.fromEncodedArray(envelope.messages); + + messages.forEach(async (message) => { + await processMessage(roomName, message); + }); + + // Acknowledge message to remove from queue + ch.ack(item); + }); + }); +}); +``` + + +### Monitor the dead letter queue + +Ably automatically provisions a [dead letter queue](/docs/platform/integrations/queues#deadletter) when you create a queue. Messages are moved to the dead letter queue when they: -### Example queue consumer +* Are rejected by consumers (`basic.reject` or `basic.nack` with `requeue=false`) +* Exceed their TTL and expire +* Cause the queue to reach maximum capacity (oldest messages dropped) + +**Important:** Monitor your dead letter queue to detect processing failures and capacity issues. ```javascript -async function consumeQueue(queueName) { - const queue = await client.queues.get(queueName); +async function monitorDeadLetterQueue(appID) { + // Dead letter queue name format: APPID:deadletter + const dlqName = `${appID}:deadletter`; + // Avoid hardcoding credentials in production + const url = 'amqps://APPID.KEYID:SECRET@us-east-1-a-queue.ably.io/shared'; + + amqp.connect(url, (err, conn) => { + if (err) { return console.error(err); } - queue.subscribe(async (message) => { - const roomName = extractRoomName(message.channel); - const decoded = decodeMessages(message); + conn.createChannel((err, ch) => { + if (err) { return console.error(err); } - await processMessages(roomName, decoded); + ch.consume(dlqName, (item) => { + const envelope = JSON.parse(item.content); + console.error('Message dropped from queue:', envelope); - // Acknowledge message - await message.ack(); + // Handle failed message (alert, retry, etc.) + handleFailedMessage(envelope); + + ch.ack(item); + }); + }); }); } ``` +**Note:** Only one dead letter queue exists per Ably application, shared across all queues. + ## Using streaming -Stream messages to external systems like Kafka or Kinesis. +[Outbound streaming](/docs/platform/integrations/streaming) enables you to stream a constant flow of chat messages to external streaming or queueing services. ### Setup -Configure streaming to your target system. See [outbound streaming](/docs/platform/integrations/streaming) for setup details. +Configure streaming to your target system. See the following for platform-specific setup: + +* [Apache Kafka](/docs/platform/integrations/streaming/kafka) - Stream to Kafka topics +* [AWS Kinesis](/docs/platform/integrations/streaming/kinesis) - Stream to Kinesis streams +* [AWS SQS](/docs/platform/integrations/streaming/sqs) - Stream to SQS queues +* [AMQP](/docs/platform/integrations/streaming/amqp) - Stream to AMQP brokers +* [Apache Pulsar](/docs/platform/integrations/streaming/pulsar) - Stream to Pulsar topics +* [General streaming documentation](/docs/platform/integrations/streaming) - Overview and configuration ### Benefits @@ -292,6 +370,88 @@ async function consumeFromKafka() { ``` +## Monitoring integration performance + +Monitor the health and performance of your integrations in realtime using Ably's [metachannels](/docs/metadata-stats/metadata/subscribe). Metachannels provide app-level metadata about integrations, statistics, and errors. + +### Monitor integration errors + +The [`[meta]log` channel](/docs/metadata-stats/metadata/subscribe#log) publishes error events from integrations in realtime. This enables you to detect and respond to integration failures as they occur. + +Subscribe to the `[meta]log` channel to receive error events: + + +```javascript +const Ably = require('ably'); +const client = new Ably.Realtime({ key: 'YOUR_API_KEY' }); + +const metaLogChannel = client.channels.get('[meta]log'); + +await metaLogChannel.subscribe((message) => { + if (message.data.error) { + alertOperationsTeam(message.data); + } +}); +``` + + +Integration error logs will contain a `tag` field as part of the `data` payload, indicating the integration type - for example, `reactor.generic.http`, for webhooks. This allows you to filter and categorize errors by integration method. + +**Note:** The `[meta]log` channel only publishes errors that cannot be directly reported to clients. For example, webhook delivery failures or queue publishing errors. + +### Monitor app statistics + +The [`[meta]stats:minute` channel](/docs/metadata-stats/metadata/subscribe#stats) publishes [app-level statistics](/docs/metadata-stats/stats) every minute. Use these statistics to monitor integration throughput, message volumes, and resource usage. + +Subscribe to receive [statistics](docs/metadata-stats/stats#outbound) updates: + + +```javascript +const statsChannel = client.channels.get('[meta]stats:minute'); + +statsChannel.subscribe('update', (event) => { + const stats = event.data.entries; + + // Monitor integration-specific metrics + console.log('Integration stats:', { + webhooks: stats['messages.outbound.webhook.messages.count'] || 0, + queues: stats['messages.outbound.sharedQueue.messages.count'] || 0, + // Add other metrics as needed + }); + + // Alert on anomalies + if (stats['messages.outbound.webhook.all.failed'] > threshold) { + alertOnWebhookFailures(stats); + } +}); +``` + + +Use the [rewind channel option](/docs/channels/options/rewind) to retrieve the most recent statistics immediately: + + +```javascript +// Get the last statistics event plus subscribe to future updates +const statsChannel = client.channels.get('[meta]stats:minute', { + params: { rewind: '1' } +}); + +statsChannel.subscribe('update', (event) => { + console.log('Stats:', event.data.entries); +}); +``` + + +### Integration monitoring best practices + +* **Subscribe to both channels**: Use `[meta]log` for error detection and `[meta]stats:minute` for performance monitoring +* **Set up alerting**: Create automated alerts for integration failures or throughput anomalies +* **Track trends**: Store statistics over time to identify patterns and capacity planning needs +* **Filter by integration type**: Use the statistics entries to monitor specific integration methods (webhooks, queues, streaming) +* **Correlate with external logs**: Use `requestId` from error events to correlate with your system logs + +See [metadata subscriptions documentation](/docs/metadata-stats/metadata/subscribe) for complete details on available metachannels. + ## Handling message reactions Message reactions are delivered as separate events with `action: 'message.summary'`. @@ -335,43 +495,6 @@ function processMessage(message) { Read more about [message reactions](/docs/chat/rooms/message-reactions) and the [reactions annotation mapping](/docs/chat/integrations#how-to-handle-message-reactions). -## Error handling and monitoring - -### Monitor integration failures - -Subscribe to the [`[meta]log` channel](/docs/platform/errors#meta) to detect integration failures: - - -```javascript -const metaChannel = client.channels.get('[meta]log:integration-rule-id'); - -metaChannel.subscribe((message) => { - if (message.data.error) { - console.error('Integration error:', message.data); - alertOnFailure(message.data); - } -}); -``` - - -### Dead letter queues - -For queue integrations, monitor the dead letter queue: - - -```javascript -async function monitorDeadLetterQueue(appID) { - const dlq = await client.queues.get(`${appID}:deadletter`); - - dlq.subscribe(async (message) => { - console.error('Message dropped from queue:', message); - await alertAndRetry(message); - }); -} -``` - - -**Note:** Only one dead letter queue exists per Ably application, shared across all queues. ## Related documentation diff --git a/src/pages/docs/guides/chat/external-integrations.mdx b/src/pages/docs/guides/chat/external-integrations.mdx index 1e8d14744d..c74458f136 100644 --- a/src/pages/docs/guides/chat/external-integrations.mdx +++ b/src/pages/docs/guides/chat/external-integrations.mdx @@ -6,168 +6,161 @@ meta_keywords: "chat integrations, external systems, webhooks, AI chat, message Ably Chat is designed for extensibility. Whether you need to process messages with AI, translate content in real-time, trigger business workflows, or enrich messages with external data, Ably's integration capabilities enable you to extend your chat application without compromising on scale, reliability, or performance. -This guide explains how to architect integrations that process chat messages through external systems and respond back to the chat in real-time. Before diving into the technical implementation, it's important to understand your architectural goals and the role your external system will play. +This guide explains how to architect integrations that process chat messages through external systems and respond back to the chat in real-time. ## Why integrate external systems? Integrating external systems with Ably Chat enables you to extend your chat application beyond simple message delivery. Common integration use cases include: -* **AI-powered assistance:** Process commands or questions through language models and respond with helpful information. -* **Real-time translation:** Automatically translate messages for multilingual chat rooms. -* **Notifications:** Detect and process mentions, or other notifications. -* **Analytics and monitoring:** Analyze sentiment, track engagement metrics, or identify trending topics. -* **Workflow automation:** Trigger external business processes based on chat activity. -* **Command processing:** Handle slash commands that invoke server-side logic. +* **AI-powered assistance:** Process commands or questions through language models and respond with helpful information +* **Real-time translation:** Automatically translate messages for multilingual chat rooms +* **Notifications:** Detect and process mentions, or trigger custom notification services +* **Analytics and monitoring:** Analyze sentiment, track engagement metrics, or identify trending topics +* **Workflow automation:** Trigger external business processes based on chat activity +* **Command processing:** Handle slash commands that invoke server-side logic ## Why Ably for external integrations? -Ably Chat integrations are built on Ably's proven platform architecture, designed to handle integration workloads at any scale: +Ably Chat integrations are built on Ably's proven platform architecture: -* **Reliable message delivery:** Every integration benefits from Ably's [four pillars of dependability](/docs/platform/architecture): Performance, Integrity, Reliability, and Availability. -* **Flexible routing:** Use channel filters to selectively route only the messages on channels that need processing to your integration. -* **Multiple integration methods:** Choose webhooks, queues, or streaming based on your architecture and scale requirements. -* **Proven at scale:** Ably processes over 500 million messages per day for customers, with [serverless scalability](/docs/platform/architecture/platform-scalability) that eliminates infrastructure management. -* **No vendor lock-in:** Integrate with any external service, using any language or platform that can handle HTTP requests or consume from queues. +* **Reliable message delivery:** Every integration benefits from Ably's [four pillars of dependability](/docs/platform/architecture): Performance, Integrity, Reliability, and Availability +* **Flexible routing:** Use channel filters to selectively route only the messages that need processing +* **Multiple integration methods:** Choose webhooks, queues, or streaming based on your architecture and scale requirements +* **Proven at scale:** Ably processes over 500 million messages per day with [serverless scalability](/docs/platform/architecture/platform-scalability) +* **No vendor lock-in:** Integrate with any external service using any language or platform Because Ably Chat rooms use Pub/Sub channels as their underlying transport, you can leverage all of Ably's [platform integrations](/docs/platform/integrations) to extend your chat application. -## Key considerations +## Understanding the integration flow -Consider the following when integrating external systems with Ably Chat: +The typical integration flow works as follows: -* **Avoiding infinite loops:** When your integration responds back to chat, ensure responses don't trigger the same integration rule. Use separate channels or the [privileged skip integration flag](/docs/platform/integrations/skip-integrations) to break potential loops. -* **Scale and reliability:** Different integration methods offer different reliability guarantees. Webhooks are simpler to implement but have limited retry windows, queues provide at-least-once delivery with dead letter queues, and streaming offers massive scale but requires more infrastructure management. -* **External system limitations:** Your external service may have rate limits, processing constraints, or availability issues. Implement retry logic, circuit breakers, and caching to handle these limitations gracefully. -* **Cost optimization:** Only process messages that require processing. Use channel filters to ensure you only receive messages from relevant chat rooms. +1. A user sends a message to a chat room, optionally including [metadata or headers](#metadata-headers) to control processing +2. An integration rule evaluates the message based on room name pattern matching +3. If the rule matches, Ably forwards the message to your external system +4. Your external system processes the message (AI inference, translation, business logic, etc.) +5. The external system [sends a response](#responding) back to the chat room +6. All subscribed clients receive the response in real-time -## Implementation options +This bidirectional flow enables powerful patterns like chatbots, automated support, real-time translation, and more. -You can configure many different types of integrations with Ably Chat. The three methods commonly used, and covered in this guide, are: +## Choosing an integration method -1. Using [outbound webhooks](#webhook). [HTTP endpoint](/docs/platform/integrations/webhooks/generic), [AWS Lambda](/docs/platform/integrations/webhooks/lambda), and others. -2. Using [Ably queues](#ably-queue). -3. Using [outbound streaming](#outbound-streaming). Stream to your own [Kafka](/docs/platform/integrations/streaming/kafka), [Kinesis](/docs/platform/integrations/streaming/kinesis), and others. +Ably provides three integration methods, each suited to different use cases: -## Understanding integrations +### Webhooks -Ably Chat rooms are built on Ably Pub/Sub channels, which means you can leverage Ably's integration capabilities to forward messages to external systems for processing: +[Outbound webhooks](/docs/platform/integrations/webhooks) are ideal for simple business logic and event-driven actions. -* **Chat rooms use Pub/Sub channels:** Each chat room is built on an Ably channel, enabling the full range of Ably's integration capabilities. -* **Message structure:** Chat messages are encoded as Pub/Sub messages with a specific structure. See the [Chat integrations documentation](/docs/chat/integrations) for details on the mapping. -* **Integration rules:** Configure rules that filter which messages trigger integrations based on channel name patterns and event types. -* **Bidirectional flow:** Integrations allow you to export messages to external systems, process them, and respond back to chat rooms in real-time. +**Best for:** +* Processing @mentions or triggering custom notifications +* Executing lightweight business logic (lookup, validation, etc.) +* Serverless architectures (AWS Lambda, Cloud Functions, etc.) -### Integration flow +**Learn more:** See [Extract messages via integrations](/docs/chat/rooms/message-extraction#webhooks) for setup details and code examples. -The typical integration flow works as follows: +### Ably Queues -1. A user sends a message to a chat room, optionally including headers or metadata to control processing externally. -2. An integration rule evaluates the message based on room name pattern matching. -3. If the rule matches, Ably forwards the message to your external system via the configured integration method. -4. Your external system processes the message (AI inference, translation, business logic, etc.). -5. The external system sends a response back to an Ably Chat room. -6. All subscribed clients receive the response in real-time. +[Ably Queues](/docs/platform/integrations/queues) provide strict ordering and fault-tolerant delivery. -[/TODO/]: # (Add a visual showing somethign like *Client → Ably Chat → Integration Rule → External System → Response → Ably Chat → Clients*) +**Best for:** +* Automated support flows where message order matters +* Processing sequential events (game moves, transaction steps) +* Fault-tolerant message processing with automatic retries -## Filtering rooms and event types +**Learn more:** See [Extract messages via integrations](/docs/chat/rooms/message-extraction#queues) for setup details and code examples. -Integrations allow you to filter which Chat rooms are forwarded to your external system using a regular expression on the room name. -This is a simple way to reduce the volume of messages you need to process by only receiving messages from the chat rooms you are interested in. +### Streaming -### Setting up integration rules +[Outbound streaming](/docs/platform/integrations/streaming) enables massive scale and integration with existing infrastructure. -When configuring an integration rule in your Ably dashboard: +**Best for:** +* High-throughput scenarios (large-scale chats, millions of messages) +* Existing streaming infrastructure (Kafka, Kinesis, etc.) +* Complex data pipelines and analytics -* **Channel filter:** Use a regular expression to match channel names. For example, `^chat:support.*` will match all channels starting with `chat:support`. See [understanding channel names and room names](#understanding-channel-names-and-room-names) for details on how room names map to channel names. -* **Event type:** Use `channel.message` for all integration types. This forwards all chat messages published to relevant rooms and excludes presence messages and channel lifecycle messages -* **Enveloped messages:** Enable this to receive all metadata about the message, including the `serial`, `version`, and `extras` (which include the [`headers`](/docs/chat/rooms/messages#structure) of a chat message) +**Learn more:** See [Extract messages via integrations](/docs/chat/rooms/message-extraction#streaming) for setup details and code examples. -### Understanding channel names and room names +## Using metadata and headers -Chat rooms are underpinned by Ably Pub/Sub channels, but with a specific suffix (`::$chat`) added to form the full channel name. -When using the Chat SDK to create or get a room, this is done automatically for you, you do not need to include the suffix yourself. +Metadata and headers enable you to control how messages are processed by external systems and add context for integration logic. -Integration rules match against the full channel name, but you don't need to include the `::$chat` suffix in your filter pattern. +### Metadata -**Example room and channel naming:** +Message [`metadata`](/docs/chat/rooms/messages#structure) is set by the client when sending a message. Use metadata for business data that should persist with the message. ```javascript -// Get a chat room - the room name becomes the channel name with ::$chat suffix -const supportRoom = await chatClient.rooms.get('chat:support'); -// Underlying channel: chat:support::$chat - -// Messages sent to these rooms will trigger an integration -// if your rule's channel filter is: ^chat:support.* -await supportRoom.messages.send({ text: 'Need help' }); - -// Messages sent to other channel patterns will NOT trigger the integration -const generalRoom = await chatClient.rooms.get('chat:general'); -// Underlying channel: chat:general::$chat -await generalRoom.messages.send({ text: 'Hi' }); // Won't trigger if filter is ^chat:support.* +// Client sends message with metadata +await room.messages.send({ + text: 'What is the weather in London?', + metadata: { + intent: 'weather-query', + location: 'London', + userId: 'user-123' + } +}); ``` -## Decoding and processing messages +**Important:** Metadata is not server-validated. Always treat it as untrusted user input in your integration code. -When your integration receives messages, you need to decode them and extract the relevant information. +### Headers -### Understanding enveloped messages - -With enveloping enabled (the default), Ably wraps messages in additional metadata like so: +Message [`headers`](/docs/chat/rooms/messages#structure) can be set by the client, but can also be added by integration rules at the server level. This makes headers useful for adding server-authoritative routing data. ```javascript -{ - "source": "channel.message", - "appId": "your-app-id", - "channel": "chat:support::$chat", - "site": "eu-west-1-A", - "ruleId": "integration-rule-id", - "messages": [ - { - "id": "oWSRIqvv2d:0:0", - "clientId": "some-client-id", - "name": "chat.message", - "timestamp": 1234567890, - "serial": "01765820788939-000@108wgxjJwBwuAB37648671:000", - "action": 0, - "data": { - "text": "Message content", - "metadata": {} - }, - "extras": { - "headers": { - "x-integration-action": "weather" - } - } - } - ] -} +// Client sends message with headers +await room.messages.send({ + text: '@bot translate "Hello, nice to meet you!"', + headers: { + 'x-command': 'translate', + 'x-target-lang': 'fr' + } +}); ``` -### Extracting the room name +Headers can also be added by your integration rule configuration in the Ably dashboard, providing server-authoritative data that clients cannot tamper with. This is useful for routing decisions or adding trusted context before messages reach your integration. + +### Metadata vs Headers -Your integration receives the full channel name including the `::$chat` suffix. To send responses back to the same room, extract the room name by removing the suffix: +Both metadata and headers are persisted with messages and accessible in your integration: + +| | Metadata | Headers | +|---|----------|---------| +| **Set by** | Client only | Client or integration rule | +| **Best for** | Non-critical client specific data | Routing logic and server-authoritative data | +| **Trusted?** | No (client-controlled) | Partially (rule-added headers are trusted) | +| **Use case** | Context, intent | Integration routing | + +## Understanding channel names and room names + +Chat rooms are underpinned by Ably Pub/Sub channels with a `::$chat` suffix added to form the full channel name. When using the Chat SDK to create or get a room, this is done automatically - you don't need to include the suffix yourself. + +Integration rules match against the full channel name, but you don't need to include the `::$chat` suffix in your filter pattern. ```javascript -// In your webhook handler or queue consumer -function processIntegrationPayload(envelopeData) { - // Extract room name from channel name - const channelName = envelopeData.channel; // e.g., "chat:support::$chat" - const roomName = channelName.replace('::$chat', ''); // e.g., "chat:support" -} +// Get a chat room - the room name becomes the channel name with ::$chat suffix +const supportRoom = await chatClient.rooms.get('chat:support'); +// Underlying channel: chat:support::$chat + +// Messages sent to these rooms will trigger an integration +// if your rule's channel filter is: ^chat:support.* +await supportRoom.messages.send({ text: 'Need help' }); + +// Messages sent to other channel patterns will NOT trigger the integration +const generalRoom = await chatClient.rooms.get('chat:general'); +// Underlying channel: chat:general::$chat +await generalRoom.messages.send({ text: 'Hi' }); // Won't trigger if filter is ^chat:support.* ``` -### Extracting message data +## Processing messages in your integration -The Ably SDK provides methods to decode messages from the enveloped payload. -From there, you can extract the text content, headers, and metadata and process them as needed. -You can use custom headers to control how messages are processed by your integration. For example, you might define an `x-integration-action` header to specify different processing actions: +When your integration receives messages, extract the metadata and headers to control processing logic. ```javascript @@ -181,126 +174,182 @@ async function processIntegrationPayload(envelopeData) { // Extract text content const text = msg.data?.text; - // Extract headers if needed + // Extract headers for routing const headers = msg.extras?.headers || {}; - const action = headers['x-integration-action']; + const command = headers['x-command']; - // Extract metadata if needed + // Extract metadata for business context (treat as untrusted) const metadata = msg.data?.metadata || {}; + const intent = metadata.intent; - // Process each message based on action - await processServiceRequest(roomName, text, action); + // Extract room name for sending responses + const roomName = envelopeData.channel.replace('::$chat', ''); + + // Route based on headers or metadata + if (command === 'translate') { + await handleTranslation(text, headers['x-target-lang'], roomName); + } else if (intent === 'weather-query') { + await handleWeatherQuery(metadata.location, roomName); + } } } +``` + -async function processServiceRequest(roomName, text, action) { - switch (action) { - case 'weather': - await handleWeatherRequest(text, roomName); - break; +For complete details on message decoding and structure, see [Extract messages via integrations](/docs/chat/rooms/message-extraction#decoding). - case 'translate': - await handleTranslationRequest(text, roomName); - break; +## Responding back to chat - case 'ai-assist': - await handleAIAssistantRequest(text, roomName); - break; +After processing messages through your external system, publish responses back to the chat room. This completes the bidirectional integration flow. - default: - // Ignore messages without integration actions - break; +### Avoiding infinite loops + +When your integration responds back to chat, ensure responses don't trigger the same integration rule. Use Ably's [skip integrations](/docs/platform/integrations/skip-integrations) feature to publish messages that bypass integration rules. + +**Note:** Your API key or token must have the [`privileged-headers`](/docs/auth/capabilities#capability-operations) capability to skip integrations. + + +```javascript +const { ChatClient } = require('@ably/chat'); + +async function sendResponseToChat(roomName, responseText) { + // Initialize Chat client with privileged API key + const chatClient = new ChatClient({ key: 'YOUR_API_KEY' }); + + // Get the chat room + const room = await chatClient.rooms.get(roomName); + + // Send response message skipping integration rules + await room.messages.send({ + text: responseText + }, { + extras: { + privileged: { + skipRule: '*' // Skip all integration rules + } } + }); } ``` -## Using a webhook +Alternatively, you can skip only specific rules by providing the rule IDs as an array instead of `'*'`. The rule ID is available in the integration webhook envelope's `ruleId` field, or you can find it in your Ably [dashboard](https://ably.com/dashboard) under Integrations. -Ably can forward messages to your external system via a webhook. This is the simplest integration method to set up and works well for most use cases. This section covers HTTP endpoint webhooks, but the same principles apply to other webhook integrations such as AWS Lambda, Azure Function, Google Cloud Function, and others. +## Complete example: AI assistant integration -Read the guide on [outbound webhooks](/docs/platform/integrations/webhooks) for more details on how to set up a webhook with Ably for the platform of your choice. +The following example demonstrates a complete bidirectional integration where messages are processed by an AI assistant and responses are sent back to the chat. -Benefits: -- Simplest integration method to implement and get started quickly. -- Automatic retry handling with configurable retry windows. -- No additional infrastructure required beyond your webhook endpoint or function. +### Step 1: Client sends message with metadata -You need to consider: -- Ably retries failed webhook deliveries, but only for a limited retry window. Monitor the [`[meta]log` channel](/docs/platform/errors#meta) for delivery failures. -- Messages can arrive out-of-order. Use `serial` and `version.serial` properties to order them correctly if needed. -- Failed webhook calls that exceed the retry window will lead to message loss. Implement monitoring and alerting to detect failures early. -- [At-least-once delivery](/docs/platform/architecture/idempotency#protocol-support-for-exactly-once-delivery) means you need to handle duplicate messages. Deduplication can be done by tracking `serial` and `version.serial`. + +```javascript +// Client application +await room.messages.send({ + text: '@bot translate "Hello!"', + metadata: { + intent: 'translate', + targetLanguage: 'fr' + } +}); +``` + -## Using an Ably queue +### Step 2: Integration rule forwards to webhook -Ably can forward messages from chat room channels to an [Ably Queue](/docs/platform/integrations/queues), which you can then consume from your own servers to process messages through your external system and respond back to chat. Read the guide on [Ably queues](/docs/platform/integrations/queues) for more details on how to set up the queue integration with Ably. +Configure an integration rule in your dashboard: +- Channel filter: `^chat:.*` (matches all chat rooms) +- Event type: `channel.message` +- Endpoint: `https://your-domain.com/webhook` -Ably ensures that each message is delivered to only one consumer even if multiple consumers are connected. +### Step 3: Webhook processes and responds -Benefits of using an Ably queue: -- You can consume it from your servers, meaning overall this is fault-tolerant. Ably takes care of the complexity of maintaining a queue. -- You can use multiple queues and configure which channels go to which queue via regex filters on the channel name. -- Fault-tolerant: if your systems suffer any temporary downtime, you will not miss messages, up to the queue max size. There is a dead letter queue to handle the situation where messages are dropped from the Ably Queue. + +```javascript +const express = require('express'); +const Ably = require('ably'); -You need to consider: -- During peak times you may need to scale up your consumers to avoid overloading the queue past the maximum queue length allowed. -- Each message has a time-to-live in the queue. The default and maximum is 60 minutes. -- Oldest messages are dropped if the maximum queue length is exceeded. Check the [dead letter queue](/docs/platform/integrations/queues#deadletter) to see if this is happening. -- Always consume messages from the [dead letter queue](/docs/platform/integrations/queues#deadletter) to monitor errors. +const app = express(); +app.use(express.json()); -## Using outbound streaming +app.post('/webhook', async (req, res) => { + const envelope = req.body; -Ably can stream messages directly to your own queueing or streaming service: Kafka, Kinesis, AMQP, SQS, Pulsar. Read the guide on [outbound streaming](/docs/platform/integrations/streaming) for more details on how to set up the streaming integration with Ably for the service of your choice. + // Decode messages + const messages = Ably.Realtime.Message.fromEncodedArray(envelope.messages); -Benefits: -- Use your existing queue system to process messages from Ably. -- You control your own queue system, so you have full control over message ingestion in terms of retry strategies, retention policies, queue lengths, and so on. + for (const msg of messages) { + const text = msg.data?.text; + const metadata = msg.data?.metadata || {}; + const roomName = envelope.channel.replace('::$chat', ''); -You need to consider: -- You need to maintain and be responsible for a reliable streaming system. If you don't already have such a system, it increases complexity on your end. -- Consistency. If your streaming system is not reachable, messages may be lost. Errors can be seen in the [`[meta]log` channel](/docs/platform/errors#meta). + // Process based on metadata intent + if (metadata.intent === 'translate') { + const translation = await translateText(text, metadata.targetLanguage); + await sendResponseToChat(roomName, `Translation: ${translation}`); + } + } -## Responding back to chat + res.status(200).send('OK'); +}); -After processing messages through your external system, you will typically want to publish responses back to the chat room. This could be answers from an AI assistant, translated text, enriched content, or status updates. -To avoid triggering the integration rule on the response message, use Ably's [skip integrations](/docs/platform/integrations/skip-integrations) feature to publish messages that bypass integration rules. +async function translateText(text, targetLang) { + // Call your translation service + return `[Translated to ${targetLang}]: ${text}`; +} -**Note:** Your API key or token must have the [`privileged-headers`](/docs/auth/capabilities#capability-operations) capability to apply the skip integration flag. +async function sendResponseToChat(roomName, responseText) { + const { ChatClient } = require('@ably/chat'); + const chatClient = new ChatClient({ key: process.env.ABLY_API_KEY }); - -```javascript -async function sendResponseToChat(channelName, responseText) { - // Extract the room name from the channel name - const roomName = channelName.replace('::$chat', ''); // e.g., "chat:support" const room = await chatClient.rooms.get(roomName); - // Send response message skipping some or all integration rules await room.messages.send({ text: responseText }, { extras: { privileged: { - skipRule: '*' // Skip all integration rules + skipRule: '*' // Prevent infinite loop } } }); } + +app.listen(3000); ``` -The rule ID is available in the integration webhook envelope's `ruleId` field, or you can find it in your Ably [dashboard](https://ably.com/dashboard) under Integrations. +### Step 4: Clients receive the response + +All clients subscribed to the room receive the AI assistant's response in real-time: + + +```javascript +room.messages.subscribe((event) => { + console.log('Received:', event.message.text); + // Output: "Translation: [Translated to fr]: Bonjour!" +}); +``` + + +## Key considerations + +Consider the following when integrating external systems with Ably Chat: + +* **Avoiding infinite loops:** Always use the [skip integrations flag](#responding) when responding back to chat to prevent your response from triggering the integration again +* **Scale and reliability:** Different integration methods offer different reliability guarantees. See [choosing an integration method](#implementation-options) for guidance +* **External system limitations:** Your external service may have rate limits, processing constraints, or availability issues. Implement retry logic, circuit breakers, and caching to handle these limitations gracefully +* **Cost optimization:** Only process messages that require processing. Use channel filters and metadata/headers to route messages efficiently +* **Security:** Always validate and sanitize client-provided metadata before using it in your integration logic ## Next steps Explore related documentation to deepen your integration knowledge: +* [Extract messages via integrations](/docs/chat/rooms/message-extraction) - Complete technical guide for setting up webhooks, queues, and streaming +* [Chat integrations](/docs/chat/integrations) - Technical reference for Chat message structure +* [Platform Integrations](/docs/platform/integrations) - Detailed setup for all integration types +* [Export Chat Data](/docs/guides/chat/export-chat) - Guide for storing chat messages long-term * [Chat SDK documentation](/docs/chat) - Comprehensive guide to Ably Chat features -* [Platform Integrations](/docs/platform/integrations) - Detailed setup for webhooks, queues, and streaming -* [Chat Integrations](/docs/chat/integrations) - Technical reference for Chat message structure -* [Export Chat Data](/docs/guides/chat/export-chat) - Guide for storing all chat messages long-term -* [Architecture Overview](/docs/platform/architecture) - Learn how Ably achieves scale and reliability -* [Authentication](/docs/auth) - Security best practices for production apps * [Chat Moderation](/docs/chat/moderation) - Filter and moderate chat content For custom integration requirements or questions, [contact Ably support](https://ably.com/support). From 27505030e070783b7b16195288cf95dd02348f05 Mon Sep 17 00:00:00 2001 From: Steven Lindsay Date: Wed, 17 Dec 2025 12:58:28 +0000 Subject: [PATCH 14/33] Add in benefits section for webhooks --- src/pages/docs/chat/rooms/message-extraction.mdx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/pages/docs/chat/rooms/message-extraction.mdx b/src/pages/docs/chat/rooms/message-extraction.mdx index 813abea86f..adc1f20785 100644 --- a/src/pages/docs/chat/rooms/message-extraction.mdx +++ b/src/pages/docs/chat/rooms/message-extraction.mdx @@ -187,12 +187,19 @@ Configure a webhook integration in your Ably dashboard pointing to your endpoint * [Cloudflare Workers](/docs/platform/integrations/webhooks/cloudflare) - Trigger Workers * [General webhook documentation](/docs/platform/integrations/webhooks) - Overview and other platforms +### Benefits +* Simplest integration method to implement. +* Automatic retry handling with configurable retry windows. +* No additional infrastructure required beyond your webhook endpoint or function. +* Messages can be batched together to reduce invocation overhead. + ### Considerations -* **Retry behavior**: For Lambda, AWS automatically retries failed invocations up to two times with delays (1 minute, then 2 minutes). For generic HTTP webhooks, Ably retries for a limited window -* **Ordering**: Messages may arrive out-of-order; use `serial` and `version.serial` to sort -* **Deduplication**: Handle potential duplicates using message serials - webhooks provide at-least-once delivery -* **Monitoring**: Use [`[meta]log` channel](/docs/metadata-stats/metadata/subscribe#log) to detect failures (see [monitoring section](#monitoring-performance)) +* **Retry behavior**: For Lambda, AWS automatically retries failed invocations up to two times with delays (1 minute, then 2 minutes). For generic HTTP webhooks, Ably retries for a limited window. +* **Ordering**: Messages may arrive out-of-order; use `serial` and `version.serial` to sort. +* **Message Loss**: Messages may be dropped if retries exceed the retry window. +* **Deduplication**: Handle potential duplicates using message serials - webhooks provide at-least-once delivery. +* **Monitoring**: Use [`[meta]log` channel](/docs/metadata-stats/metadata/subscribe#log) to detect failures (see [monitoring section](#monitoring-performance)). ### Example webhook handler From a2996e8e9dd560170f45bebfacf1dd3461c4e7f1 Mon Sep 17 00:00:00 2001 From: Steven Lindsay Date: Wed, 17 Dec 2025 12:59:03 +0000 Subject: [PATCH 15/33] Update nav pane --- src/data/nav/chat.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/nav/chat.ts b/src/data/nav/chat.ts index f2e3d5f4a4..d5f770f79c 100644 --- a/src/data/nav/chat.ts +++ b/src/data/nav/chat.ts @@ -113,7 +113,7 @@ export default { link: '/docs/chat/rooms/replies', }, { - name: 'Message Extraction', + name: 'Message extraction', link: '/docs/chat/rooms/message-extraction', }, ], From b7d225faf2cd9b4b2acdb7b1c00f9e42f0b5acbe Mon Sep 17 00:00:00 2001 From: Steven Lindsay Date: Wed, 17 Dec 2025 14:55:49 +0000 Subject: [PATCH 16/33] wip --- src/pages/docs/chat/rooms/message-extraction.mdx | 2 +- .../docs/guides/chat/external-integrations.mdx | 15 +++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/pages/docs/chat/rooms/message-extraction.mdx b/src/pages/docs/chat/rooms/message-extraction.mdx index adc1f20785..b56fd02b26 100644 --- a/src/pages/docs/chat/rooms/message-extraction.mdx +++ b/src/pages/docs/chat/rooms/message-extraction.mdx @@ -416,7 +416,7 @@ Subscribe to receive [statistics](docs/metadata-stats/stats#outbound) updates: ```javascript const statsChannel = client.channels.get('[meta]stats:minute'); -statsChannel.subscribe('update', (event) => { +await statsChannel.subscribe('update', (event) => { const stats = event.data.entries; // Monitor integration-specific metrics diff --git a/src/pages/docs/guides/chat/external-integrations.mdx b/src/pages/docs/guides/chat/external-integrations.mdx index c74458f136..ec8f31eeb0 100644 --- a/src/pages/docs/guides/chat/external-integrations.mdx +++ b/src/pages/docs/guides/chat/external-integrations.mdx @@ -87,16 +87,15 @@ Metadata and headers enable you to control how messages are processed by externa ### Metadata -Message [`metadata`](/docs/chat/rooms/messages#structure) is set by the client when sending a message. Use metadata for business data that should persist with the message. +Message [`metadata`](/docs/chat/rooms/messages#structure) is set by the client when sending a message. Use metadata for non-critical, client-specific information that helps your integration understand the message context. ```javascript // Client sends message with metadata await room.messages.send({ - text: 'What is the weather in London?', + text: '@mention userId-123, how are you today?', metadata: { - intent: 'weather-query', - location: 'London', + intent: 'mention', userId: 'user-123' } }); @@ -235,9 +234,9 @@ async function sendResponseToChat(roomName, responseText) { Alternatively, you can skip only specific rules by providing the rule IDs as an array instead of `'*'`. The rule ID is available in the integration webhook envelope's `ruleId` field, or you can find it in your Ably [dashboard](https://ably.com/dashboard) under Integrations. -## Complete example: AI assistant integration +## Complete example: Live translation -The following example demonstrates a complete bidirectional integration where messages are processed by an AI assistant and responses are sent back to the chat. +The following example demonstrates a complete bidirectional integration where messages are processed, translated, and responses are sent back to the chat. ### Step 1: Client sends message with metadata @@ -245,10 +244,10 @@ The following example demonstrates a complete bidirectional integration where me ```javascript // Client application await room.messages.send({ - text: '@bot translate "Hello!"', + text: "Hello!", metadata: { intent: 'translate', - targetLanguage: 'fr' + targetLanguage: 'fr' // User specified target language } }); ``` From 896593684463e9b455a5e60c2da33ec396450114 Mon Sep 17 00:00:00 2001 From: Steven Lindsay Date: Wed, 17 Dec 2025 17:34:20 +0000 Subject: [PATCH 17/33] wip --- .../guides/chat/external-integrations.mdx | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/pages/docs/guides/chat/external-integrations.mdx b/src/pages/docs/guides/chat/external-integrations.mdx index ec8f31eeb0..741948ae0b 100644 --- a/src/pages/docs/guides/chat/external-integrations.mdx +++ b/src/pages/docs/guides/chat/external-integrations.mdx @@ -87,7 +87,7 @@ Metadata and headers enable you to control how messages are processed by externa ### Metadata -Message [`metadata`](/docs/chat/rooms/messages#structure) is set by the client when sending a message. Use metadata for non-critical, client-specific information that helps your integration understand the message context. +Message [`metadata`](/docs/chat/rooms/messages#structure) is set by the client when sending a message. Use metadata for business data that should persist with the message. ```javascript @@ -106,7 +106,7 @@ await room.messages.send({ ### Headers -Message [`headers`](/docs/chat/rooms/messages#structure) can be set by the client, but can also be added by integration rules at the server level. This makes headers useful for adding server-authoritative routing data. +Message [`headers`](/docs/chat/rooms/messages#structure) can be set by the client when sending a message, similar to metadata. However, headers can also be added by integration rules when the rule triggers. ```javascript @@ -121,7 +121,16 @@ await room.messages.send({ ``` -Headers can also be added by your integration rule configuration in the Ably dashboard, providing server-authoritative data that clients cannot tamper with. This is useful for routing decisions or adding trusted context before messages reach your integration. +#### Adding headers at the rule level + +When configuring an integration rule in your Ably dashboard, you can specify headers to be added when the rule triggers. This allows you to attach additional metadata to messages only when they match your integration criteria. + +For example, you might add: +- A `x-integration-type` header to indicate the processing type (e.g., "translation", "moderation") + +These rule-level headers are added after the client publishes the message, so they appear in the enveloped message received by your integration endpoint but not in the original message stored in chat history. + +**Important:** While rule-level headers cannot be set or modified by clients, neither headers nor metadata should be treated as fully server-authoritative. Always validate and sanitize all data in your integration code before using it for business logic or security decisions. ### Metadata vs Headers @@ -130,9 +139,12 @@ Both metadata and headers are persisted with messages and accessible in your int | | Metadata | Headers | |---|----------|---------| | **Set by** | Client only | Client or integration rule | -| **Best for** | Non-critical client specific data | Routing logic and server-authoritative data | -| **Trusted?** | No (client-controlled) | Partially (rule-added headers are trusted) | -| **Use case** | Context, intent | Integration routing | +| **Added when** | On message publish | On message publish (client) or when rule triggers (rule-level) | +| **Best for** | Business data that should persist | Routing logic and integration control | +| **Trusted?** | No (always client-controlled) | No (validate both client and rule-added headers) | +| **Use case** | User IDs, context, intent | Integration routing, action types, rule metadata | + +**Key distinction:** The main advantage of headers over metadata is that headers can be added by integration rules when they trigger, allowing you to attach additional context to messages only when they're being processed by your integration. ## Understanding channel names and room names From 5e71998498a99ef1dd75137afdc6de4701147d23 Mon Sep 17 00:00:00 2001 From: Steven Lindsay Date: Thu, 18 Dec 2025 15:11:55 +0000 Subject: [PATCH 18/33] Add "External storage and processing" section to chat navigation --- src/data/nav/chat.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/data/nav/chat.ts b/src/data/nav/chat.ts index d5f770f79c..73f58401e8 100644 --- a/src/data/nav/chat.ts +++ b/src/data/nav/chat.ts @@ -118,6 +118,24 @@ export default { }, ], }, + { + name: 'External storage and processing', + pages: [ + { + name: 'Overview', + link: '/docs/chat/external-storage-and-processing', + index: true, + }, + { + name: 'Data extraction', + link: '/docs/chat/external-storage-and-processing/data-extraction', + }, + { + name: 'Data storage', + link: '/docs/chat/external-storage-and-processing/data-storage', + }, + ], + }, { name: 'React UI Kit', pages: [ From cb72bb31c740f980658127910576d831d839e1a0 Mon Sep 17 00:00:00 2001 From: Steven Lindsay Date: Thu, 18 Dec 2025 15:12:11 +0000 Subject: [PATCH 19/33] Move data extraction section --- .../data-extraction.mdx | 604 ++++++++++++++++++ 1 file changed, 604 insertions(+) create mode 100644 src/pages/docs/chat/external-storage-and-processing/data-extraction.mdx diff --git a/src/pages/docs/chat/external-storage-and-processing/data-extraction.mdx b/src/pages/docs/chat/external-storage-and-processing/data-extraction.mdx new file mode 100644 index 0000000000..4fc80b6132 --- /dev/null +++ b/src/pages/docs/chat/external-storage-and-processing/data-extraction.mdx @@ -0,0 +1,604 @@ +--- +title: "Extract messages via integrations" +meta_description: "Extract chat messages from Ably Chat using integrations for external processing, storage, or analysis." +meta_keywords: "chat integrations, message extraction, webhooks, streaming, queues, message processing, chat data" +--- + +Extract chat messages from Ably Chat rooms to external systems using Ably's integration capabilities. This enables you to process, store, or analyze messages outside of Ably Chat while maintaining realtime message delivery to chat participants. + +Chat rooms are built on Ably Pub/Sub channels, allowing you to leverage the full range of Ably's [platform integrations](/docs/platform/integrations) to forward messages to external systems. + +## Integration methods + +Ably provides three primary methods for extracting chat messages: + +1. **[Webhooks](#webhooks)** - Forward messages to [HTTP endpoints](/docs/platform/integrations/webhooks/generic), [AWS Lambda](/docs/platform/integrations/webhooks/lambda), [Google Cloud Functions](/docs/platform/integrations/webhooks/gcp-function), [Cloudflare Workers](/docs/platform/integrations/webhooks/cloudflare), and more +2. **[Queues](#queues)** - Route messages to [Ably-managed queues](/docs/platform/integrations/queues) for consumption by your services +3. **[Streaming](#streaming)** - Stream messages to external systems like [Kafka](/docs/platform/integrations/streaming/kafka), [Kinesis](/docs/platform/integrations/streaming/kinesis), [SQS](/docs/platform/integrations/streaming/sqs), [AMQP](/docs/platform/integrations/streaming/amqp), or [Pulsar](/docs/platform/integrations/streaming/pulsar) + +Each method offers different trade-offs in terms of simplicity, reliability, and infrastructure requirements. + +## Filtering rooms + +Control which chat rooms trigger integrations using channel name filters. + +### Setting up filters + +When configuring an integration rule in your Ably dashboard: + +* **Channel filter**: Use a regular expression to match room names. For example, `^support:.*` matches all rooms starting with `support:` +* **Event type**: Use `channel.message` to forward chat messages and exclude presence events +* **Enveloped messages**: Enable to receive full message metadata including `serial`, `version`, and `headers` + +**Note**: Chat rooms use the `::$chat` suffix on channel names internally. Integration filters match against the full channel name, but you don't need to include the suffix in your filter pattern. + +### Example filter configuration + + +```javascript +// Room name: support:ticket-123 +// Internal channel: support:ticket-123::$chat +// Filter pattern: ^support:.* +// Result: Messages from this room will be forwarded + +const supportRoom = await chatClient.rooms.get('support:ticket-123'); +await supportRoom.messages.send({ text: 'Help needed' }); // Will trigger integration +``` + + +## Understanding channel names and room names + +Chat rooms are underpinned by Ably Pub/Sub channels with a `::$chat` suffix added to form the full channel name. When using the Chat SDK to create or get a room, this is done automatically - you don't need to include the suffix yourself. + +Integration rules match against the full channel name, but you don't need to include the `::$chat` suffix in your filter pattern. + + +```javascript +// Get a chat room - the room name becomes the channel name with ::$chat suffix +const supportRoom = await chatClient.rooms.get('chat:support'); +// Underlying channel: chat:support::$chat + +// Messages sent to these rooms will trigger an integration +// if your rule's channel filter is: ^chat:support.* +await supportRoom.messages.send({ text: 'Need help' }); + +// Messages sent to other channel patterns will NOT trigger the integration +const generalRoom = await chatClient.rooms.get('chat:general'); +// Underlying channel: chat:general::$chat +await generalRoom.messages.send({ text: 'Hi' }); // Won't trigger if filter is ^chat:support.* +``` + + +## Decoding messages + +Messages received through integrations are encoded as Ably Pub/Sub messages and need to be decoded into Chat messages. Full details on the mapping are available in the [Chat integrations](/docs/chat/integrations) documentation. + +### Understanding enveloped messages + +With enveloping enabled (recommended), messages arrive wrapped in metadata: + + +```javascript +{ + "source": "channel.message", + "appId": "your-app-id", + "channel": "support:ticket-123::$chat", + "ruleId": "integration-rule-id", + "messages": [ + { + "id": "unique-message-id", + "clientId": "user-123", + "name": "chat.message", + "timestamp": 1234567890, + "serial": "01765820788939-000@108wgxjJwBwuAB37648671:000", + "action": 0, + "data": { + "text": "Message content", + "metadata": {} + }, + "extras": { + "headers": {} + } + } + ] +} +``` + + +### Extracting room name + +Remove the `::$chat` suffix to get the room name: + + +```javascript +function extractRoomName(channelName) { + // channelName: "support:ticket-123::$chat" + return channelName.replace('::$chat', ''); // Returns: "support:ticket-123" +} +``` + + +### Decoding message data + +Use the Ably SDK to decode messages from the envelope: + + +```javascript +const Ably = require('ably'); + +function decodeMessages(envelopeData) { + const decodedMessages = Ably.Realtime.Message.fromEncodedArray(envelopeData.messages); + + return decodedMessages.map(msg => ({ + serial: msg.serial, + text: msg.data?.text, + metadata: msg.data?.metadata || {}, + headers: msg.extras?.headers || {}, + clientId: msg.clientId, + timestamp: msg.timestamp, + action: msg.action, + versionSerial: msg.version?.serial || msg.serial + })); +} +``` + + +## Using metadata and headers + +Metadata and headers enable you to control how messages are processed by external systems and add context for integration logic. + +### Metadata + +Message [`metadata`](/docs/chat/rooms/messages#structure) is set by the client when sending a message. Use metadata for business data that should persist with the message. + + +```javascript +// Client sends message with metadata +await room.messages.send({ + text: 'What is the weather in London?', + metadata: { + intent: 'weather-query', + location: 'London', + userId: 'user-123' + } +}); +``` + + +**Important:** Metadata is not server-validated. Always treat it as untrusted user input in your integration code. + +### Headers + +Message [`headers`](/docs/chat/rooms/messages#structure) can be set by the client when sending a message, similar to metadata. However, headers can also be added by integration rules when the rule triggers. + + +```javascript +// Client sends message with headers +await room.messages.send({ + text: '@bot translate to French', + headers: { + 'x-command': 'translate', + 'x-target-lang': 'fr' + } +}); +``` + + +#### Adding headers at the rule level + +When configuring an integration rule in your Ably dashboard, you can specify headers to be added when the rule triggers. This allows you to attach additional metadata to messages only when they match your integration criteria. + +For example, you might add: +- A `x-rule-id` header to identify which rule processed the message +- A `x-processed-timestamp` header to track when the rule was triggered +- A `x-integration-type` header to indicate the processing type (e.g., "translation", "moderation") + +These rule-level headers are added after the client publishes the message, so they appear in the enveloped message received by your integration endpoint but not in the original message stored in chat history. + +**Important:** While rule-level headers cannot be set or modified by clients, neither headers nor metadata should be treated as fully server-authoritative. Always validate and sanitize all data in your integration code before using it for business logic or security decisions. + +### Metadata vs Headers + +Both metadata and headers are persisted with messages and accessible in your integration: + +| | Metadata | Headers | +|---|----------|---------| +| **Set by** | Client only | Client or integration rule | +| **Added when** | On message publish | On message publish (client) or when rule triggers (rule-level) | +| **Best for** | Business data that should persist | Routing logic and integration control | +| **Trusted?** | No (always client-controlled) | No (validate both client and rule-added headers) | +| **Use case** | User IDs, context, intent | Integration routing, action types, rule metadata | + +**Key distinction:** The main advantage of headers over metadata is that headers can be added by integration rules when they trigger, allowing you to attach additional context to messages only when they're being processed by your integration. + +## Message versioning + +Chat messages have a versioning system for updates and deletes: + +* **`serial`**: Unique identifier for the original message +* **`version.serial`**: Identifier for the specific message version +* **`action`**: Indicates message type (create, update, delete, or summary for reactions) + +### Handling message versions + + +```javascript +function processMessage(message) { + // Discard reaction summaries if not needed + if (message.action === 'message.summary') { + return; + } + + // Process based on action + switch (message.action) { + case 'message.created': + handleNewMessage(message); + break; + case 'message.updated': + handleMessageUpdate(message); + break; + case 'message.deleted': + handleMessageDelete(message); + break; + } +} +``` + + +### Version ordering + +Version serials are lexicographically ordered. A higher `version.serial` indicates a newer version: + + +```javascript +function isNewerVersion(existingVersion, newVersion) { + return newVersion > existingVersion; +} + +// Only process if this is a newer version +if (isNewerVersion(stored.version.serial, incoming.version.serial)) { + updateMessage(incoming); +} +``` + + +Read more about [message versioning and sorting](/docs/chat/rooms/messages#ordering-update-delete) in the messages documentation. + +## Using webhooks + +[Outbound webhooks](/docs/platform/integrations/webhooks) enable you to forward messages to HTTP endpoints or serverless functions. + +### Setup + +Configure a webhook integration in your Ably dashboard pointing to your endpoint. See the following for platform-specific setup guides: + +* [Generic HTTP webhook](/docs/platform/integrations/webhooks/generic) - Forward to any HTTP/HTTPS endpoint +* [AWS Lambda](/docs/platform/integrations/webhooks/lambda) - Trigger Lambda functions +* [Google Cloud Functions](/docs/platform/integrations/webhooks/gcp-function) - Invoke GCP functions +* [Cloudflare Workers](/docs/platform/integrations/webhooks/cloudflare) - Trigger Workers +* [General webhook documentation](/docs/platform/integrations/webhooks) - Overview and other platforms + +### Benefits +* Simplest integration method to implement. +* Automatic retry handling with configurable retry windows. +* No additional infrastructure required beyond your webhook endpoint or function. +* Messages can be batched together to reduce invocation overhead. + +### Considerations + +* **Retry behavior**: For Lambda, AWS automatically retries failed invocations up to two times with delays (1 minute, then 2 minutes). For generic HTTP webhooks, Ably retries for a limited window. +* **Ordering**: Messages may arrive out-of-order; use `serial` and `version.serial` to sort. +* **Message Loss**: Messages may be dropped if retries exceed the retry window. +* **Deduplication**: Handle potential duplicates using message serials - webhooks provide at-least-once delivery. +* **Monitoring**: Use [`[meta]log` channel](/docs/metadata-stats/metadata/subscribe#log) to detect failures (see [monitoring section](#monitoring-performance)). + +### Example webhook handler + + +```javascript +async function handleWebhook(req, res) { + const envelope = req.body; + const roomName = extractRoomName(envelope.channel); + const messages = decodeMessages(envelope); + + for (const message of messages) { + await processMessage(roomName, message); + } + + res.status(200).send('OK'); +} +``` + + +## Using queues + +[Ably Queues](/docs/platform/integrations/queues) are traditional message queues that enable you to consume, process, or store chat messages from your servers. + +### Setup + +Configure an Ably queue integration in your dashboard: + +1. [Provision a queue](/docs/platform/integrations/queues#provision) with your desired region, TTL, and max length settings +2. [Create a queue rule](/docs/platform/integrations/queues#config) to route chat messages to the queue +3. Consume messages using [AMQP](/docs/platform/integrations/queues#amqp) or [STOMP](/docs/platform/integrations/queues#stomp) protocols + +See [Ably queues](/docs/platform/integrations/queues) for complete setup details. + +### Benefits + +* Fault-tolerant message delivery +* Messages persist during consumer downtime (up to queue limits) +* Dead letter queue for dropped messages +* At-least-once delivery guarantee + +### Considerations + +* **Queue limits**: Default maximum is 10,000 messages. Monitor queue length during peak times to avoid reaching capacity +* **TTL**: Messages expire after 60 minutes (default/max) if not consumed +* **Dead letter queue**: Automatically provisioned. Always consume to monitor for dropped or expired messages +* **Throughput**: Multi-tenanted queues support up to 200 messages/second per account. For higher volumes, consider [dedicated queues or streaming](/docs/platform/integrations/queues#scalability) +* **Ordering**: Messages maintain order per channel with a single consumer. Multiple consumers or multi-channel messages may affect ordering + +### Example queue consumer (AMQP) + + +```javascript +const amqp = require('amqplib'); + +// Queue name format: APPID:queue-name +const queueName = 'your-app-id:chat-messages'; +// Avoid hardcoding credentials in production +const url = 'amqps://APPID.KEYID:SECRET@us-east-1-a-queue.ably.io/shared'; + +amqp.connect(url, (err, conn) => { + if (err) { return console.error(err); } + + conn.createChannel((err, ch) => { + if (err) { return console.error(err); } + + // Subscribe to the queue + ch.consume(queueName, (item) => { + const envelope = JSON.parse(item.content); + const roomName = extractRoomName(envelope.channel); + + // Decode messages using Ably SDK + const messages = Ably.Realtime.Message.fromEncodedArray(envelope.messages); + + messages.forEach(async (message) => { + await processMessage(roomName, message); + }); + + // Acknowledge message to remove from queue + ch.ack(item); + }); + }); +}); +``` + + +### Monitor the dead letter queue + +Ably automatically provisions a [dead letter queue](/docs/platform/integrations/queues#deadletter) when you create a queue. Messages are moved to the dead letter queue when they: + +* Are rejected by consumers (`basic.reject` or `basic.nack` with `requeue=false`) +* Exceed their TTL and expire +* Cause the queue to reach maximum capacity (oldest messages dropped) + +**Important:** Monitor your dead letter queue to detect processing failures and capacity issues. + + +```javascript +async function monitorDeadLetterQueue(appID) { + // Dead letter queue name format: APPID:deadletter + const dlqName = `${appID}:deadletter`; + // Avoid hardcoding credentials in production + const url = 'amqps://APPID.KEYID:SECRET@us-east-1-a-queue.ably.io/shared'; + + amqp.connect(url, (err, conn) => { + if (err) { return console.error(err); } + + conn.createChannel((err, ch) => { + if (err) { return console.error(err); } + + ch.consume(dlqName, (item) => { + const envelope = JSON.parse(item.content); + console.error('Message dropped from queue:', envelope); + + // Handle failed message (alert, retry, etc.) + handleFailedMessage(envelope); + + ch.ack(item); + }); + }); + }); +} +``` + + +**Note:** Only one dead letter queue exists per Ably application, shared across all queues. + +## Using streaming + +[Outbound streaming](/docs/platform/integrations/streaming) enables you to stream a constant flow of chat messages to external streaming or queueing services. + +### Setup + +Configure streaming to your target system. See the following for platform-specific setup: + +* [Apache Kafka](/docs/platform/integrations/streaming/kafka) - Stream to Kafka topics +* [AWS Kinesis](/docs/platform/integrations/streaming/kinesis) - Stream to Kinesis streams +* [AWS SQS](/docs/platform/integrations/streaming/sqs) - Stream to SQS queues +* [AMQP](/docs/platform/integrations/streaming/amqp) - Stream to AMQP brokers +* [Apache Pulsar](/docs/platform/integrations/streaming/pulsar) - Stream to Pulsar topics +* [General streaming documentation](/docs/platform/integrations/streaming) - Overview and configuration + +### Benefits + +* Leverage existing streaming infrastructure +* Full control over retention and processing +* Massive scale capabilities + +### Considerations + +* **Infrastructure**: You manage and maintain the streaming system +* **Reliability**: Messages lost if system unavailable +* **Complexity**: Higher operational overhead + +### Example Kafka consumer + + +```javascript +const { Kafka } = require('kafkajs'); + +async function consumeFromKafka() { + const consumer = kafka.consumer({ groupId: 'chat-processor' }); + await consumer.connect(); + await consumer.subscribe({ topic: 'ably-chat-messages' }); + + await consumer.run({ + eachMessage: async ({ message }) => { + const envelope = JSON.parse(message.value); + const roomName = extractRoomName(envelope.channel); + const decoded = decodeMessages(envelope); + + await processMessages(roomName, decoded); + } + }); +} +``` + + +## Monitoring integration performance + +Monitor the health and performance of your integrations in realtime using Ably's [metachannels](/docs/metadata-stats/metadata/subscribe). Metachannels provide app-level metadata about integrations, statistics, and errors. + +### Monitor integration errors + +The [`[meta]log` channel](/docs/metadata-stats/metadata/subscribe#log) publishes error events from integrations in realtime. This enables you to detect and respond to integration failures as they occur. + +Subscribe to the `[meta]log` channel to receive error events: + + +```javascript +const Ably = require('ably'); +const client = new Ably.Realtime({ key: 'YOUR_API_KEY' }); + +const metaLogChannel = client.channels.get('[meta]log'); + +await metaLogChannel.subscribe((message) => { + if (message.data.error) { + alertOperationsTeam(message.data); + } +}); +``` + + +Integration error logs will contain a `tag` field as part of the `data` payload, indicating the integration type - for example, `reactor.generic.http`, for webhooks. This allows you to filter and categorize errors by integration method. + +**Note:** The `[meta]log` channel only publishes errors that cannot be directly reported to clients. For example, webhook delivery failures or queue publishing errors. + +### Monitor app statistics + +The [`[meta]stats:minute` channel](/docs/metadata-stats/metadata/subscribe#stats) publishes [app-level statistics](/docs/metadata-stats/stats) every minute. Use these statistics to monitor integration throughput, message volumes, and resource usage. + +Subscribe to receive [statistics](docs/metadata-stats/stats#outbound) updates: + + +```javascript +const statsChannel = client.channels.get('[meta]stats:minute'); + +await statsChannel.subscribe('update', (event) => { + const stats = event.data.entries; + + // Monitor integration-specific metrics + console.log('Integration stats:', { + webhooks: stats['messages.outbound.webhook.messages.count'] || 0, + queues: stats['messages.outbound.sharedQueue.messages.count'] || 0, + // Add other metrics as needed + }); + + // Alert on anomalies + if (stats['messages.outbound.webhook.all.failed'] > threshold) { + alertOnWebhookFailures(stats); + } +}); +``` + + +Use the [rewind channel option](/docs/channels/options/rewind) to retrieve the most recent statistics immediately: + + +```javascript +// Get the last statistics event plus subscribe to future updates +const statsChannel = client.channels.get('[meta]stats:minute', { + params: { rewind: '1' } +}); + +statsChannel.subscribe('update', (event) => { + console.log('Stats:', event.data.entries); +}); +``` + + +### Integration monitoring best practices + +* **Subscribe to both channels**: Use `[meta]log` for error detection and `[meta]stats:minute` for performance monitoring +* **Set up alerting**: Create automated alerts for integration failures or throughput anomalies +* **Track trends**: Store statistics over time to identify patterns and capacity planning needs +* **Filter by integration type**: Use the statistics entries to monitor specific integration methods (webhooks, queues, streaming) +* **Correlate with external logs**: Use `requestId` from error events to correlate with your system logs + +See [metadata subscriptions documentation](/docs/metadata-stats/metadata/subscribe) for complete details on available metachannels. + +## Handling message reactions + +Message reactions are delivered as separate events with `action: 'message.summary'`. + +### Reaction summary structure + + +```javascript +{ + "action": "message.summary", + "serial": "original-message-serial", + "annotations": { + "summary": { + "reaction:unique.v1": { + "👍": { "count": 5 }, + "❤️": { "count": 3 } + } + } + } +} +``` + + +### Processing reactions + + +```javascript +function processMessage(message) { + if (message.action === 'message.summary') { + // Handle reaction summary + const reactions = message.annotations?.summary?.['reaction:unique.v1'] || {}; + updateReactionCounts(message.serial, reactions); + return; + } + + // Handle regular message + saveMessage(message); +} +``` + + +Read more about [message reactions](/docs/chat/rooms/message-reactions) and the [reactions annotation mapping](/docs/chat/integrations#how-to-handle-message-reactions). + + +## Related documentation + +* [Chat integrations](/docs/chat/integrations) - Technical reference for message structure mapping +* [Platform integrations](/docs/platform/integrations) - Complete integration setup guides +* [Message structure](/docs/chat/rooms/messages#structure) - Chat message format details +* [Chat history](/docs/chat/rooms/history) - Retrieve historical messages via API +* [Store messages in your database](/docs/chat/external-storage-and-processing/data-storage) - Guide for long-term storage +* [Process and respond to messages](/docs/chat/external-storage-and-processing/data-processing) - Guide for bidirectional integrations \ No newline at end of file From 8ca5bba12736b4af1c2e2dd7eda979a495feadf1 Mon Sep 17 00:00:00 2001 From: Steven Lindsay Date: Thu, 18 Dec 2025 15:12:42 +0000 Subject: [PATCH 20/33] Create a new document specific to data-processing --- .../data-processing.mdx | 492 ++++++++++++++++++ 1 file changed, 492 insertions(+) create mode 100644 src/pages/docs/chat/external-storage-and-processing/data-processing.mdx diff --git a/src/pages/docs/chat/external-storage-and-processing/data-processing.mdx b/src/pages/docs/chat/external-storage-and-processing/data-processing.mdx new file mode 100644 index 0000000000..cebd4a172e --- /dev/null +++ b/src/pages/docs/chat/external-storage-and-processing/data-processing.mdx @@ -0,0 +1,492 @@ +--- +title: "Process and respond to messages" +meta_description: "Process chat messages through external systems and respond back to chat in real-time with AI, translation, moderation, and more." +meta_keywords: "chat processing, AI chat, translation, bidirectional integration, message processing, chat automation" +--- + +Process chat messages through external systems like AI assistants, translation services, or moderation tools, then respond back to the chat in real-time. This bidirectional integration pattern enables powerful features like chatbots, automated support, real-time translation, and more. + +This guide explains how to architect integrations that process messages and respond back to chat, completing the bidirectional flow. + +## Why process messages with external systems? + +Integrating external systems with Ably Chat enables you to extend your chat application beyond simple message delivery: + +* **AI-powered assistance**: Process commands or questions through language models and respond with helpful information +* **Real-time translation**: Automatically translate messages for multilingual chat rooms +* **Content moderation**: Filter messages through moderation services before they appear in chat +* **Notifications**: Detect mentions or keywords and trigger custom notification systems +* **Workflow automation**: Trigger external business processes based on chat activity +* **Command processing**: Handle slash commands that invoke server-side logic + +## Understanding the bidirectional flow + +The typical bidirectional integration flow works as follows: + +1. A user sends a message to a chat room, optionally including [metadata or headers](#metadata-headers) to control processing +2. An integration rule evaluates the message based on room name pattern matching +3. If the rule matches, Ably forwards the message to your external system +4. Your external system processes the message (AI inference, translation, business logic, etc.) +5. The external system [sends a response](#responding) back to the chat room +6. All subscribed clients receive the response in real-time + +This bidirectional flow enables powerful patterns like chatbots, automated support, real-time translation, and more. + +## Choosing an integration method + +Ably provides three integration methods, each suited to different use cases. All three support the bidirectional pattern described in this guide. + +### Webhooks + +[Outbound webhooks](/docs/platform/integrations/webhooks) are ideal for simple business logic and event-driven actions. + +**Best for:** +* Processing @mentions or triggering custom notifications +* Executing lightweight business logic (lookup, validation, etc.) +* Serverless architectures (AWS Lambda, Cloud Functions, etc.) + +**Learn more:** See [Extract messages via integrations](/docs/chat/external-storage-and-processing/data-extraction#webhooks) for setup details and code examples. + +### Ably Queues + +[Ably Queues](/docs/platform/integrations/queues) provide strict ordering and fault-tolerant delivery. + +**Best for:** +* Automated support flows where message order matters +* Processing sequential events (game moves, transaction steps) +* Fault-tolerant message processing with automatic retries + +**Learn more:** See [Extract messages via integrations](/docs/chat/external-storage-and-processing/data-extraction#queues) for setup details and code examples. + +### Streaming + +[Outbound streaming](/docs/platform/integrations/streaming) enables massive scale and integration with existing infrastructure. + +**Best for:** +* High-throughput scenarios (large-scale chats, millions of messages) +* Existing streaming infrastructure (Kafka, Kinesis, etc.) +* Complex data pipelines and analytics + +**Learn more:** See [Extract messages via integrations](/docs/chat/external-storage-and-processing/data-extraction#streaming) for setup details and code examples. + +## Using metadata and headers for routing + +Metadata and headers enable you to control how messages are processed by external systems and add context for integration logic. + +### Metadata + +Message [`metadata`](/docs/chat/rooms/messages#structure) is set by the client when sending a message. Use metadata for business data that should persist with the message. + + +```javascript +// Client sends message with metadata +await room.messages.send({ + text: 'What is the weather in London?', + metadata: { + intent: 'weather-query', + location: 'London', + userId: 'user-123' + } +}); +``` + + +**Important:** Metadata is not server-validated. Always treat it as untrusted user input in your integration code. + +### Headers + +Message [`headers`](/docs/chat/rooms/messages#structure) can be set by the client when sending a message, similar to metadata. However, headers can also be added by integration rules when the rule triggers. + + +```javascript +// Client sends message with headers +await room.messages.send({ + text: '@bot translate to French', + headers: { + 'x-command': 'translate', + 'x-target-lang': 'fr' + } +}); +``` + + +#### Adding headers at the rule level + +When configuring an integration rule in your Ably dashboard, you can specify headers to be added when the rule triggers. This allows you to attach additional metadata to messages only when they match your integration criteria. + +For example, you might add: +- A `x-rule-id` header to identify which rule processed the message +- A `x-processed-timestamp` header to track when the rule was triggered +- A `x-integration-type` header to indicate the processing type (e.g., "translation", "moderation") + +These rule-level headers are added after the client publishes the message, so they appear in the enveloped message received by your integration endpoint but not in the original message stored in chat history. + +**Important:** While rule-level headers cannot be set or modified by clients, neither headers nor metadata should be treated as fully server-authoritative. Always validate and sanitize all data in your integration code before using it for business logic or security decisions. + +### Metadata vs Headers + +Both metadata and headers are persisted with messages and accessible in your integration: + +| | Metadata | Headers | +|---|----------|---------| +| **Set by** | Client only | Client or integration rule | +| **Added when** | On message publish | On message publish (client) or when rule triggers (rule-level) | +| **Best for** | Business data that should persist | Routing logic and integration control | +| **Trusted?** | No (always client-controlled) | No (validate both client and rule-added headers) | +| **Use case** | User IDs, context, intent | Integration routing, action types, rule metadata | + +**Key distinction:** The main advantage of headers over metadata is that headers can be added by integration rules when they trigger, allowing you to attach additional context to messages only when they're being processed by your integration. + +## Processing messages in your integration + +When your integration receives messages, extract the metadata and headers to control processing logic. + + +```javascript +const Ably = require('ably'); + +async function processIntegrationPayload(envelopeData) { + // Decode messages using Ably SDK + const decodedMessages = Ably.Realtime.Message.fromEncodedArray(envelopeData.messages); + + for (const msg of decodedMessages) { + // Extract text content + const text = msg.data?.text; + + // Extract headers for routing + const headers = msg.extras?.headers || {}; + const command = headers['x-command']; + + // Extract metadata for business context (treat as untrusted) + const metadata = msg.data?.metadata || {}; + const intent = metadata.intent; + + // Extract room name for sending responses + const roomName = envelopeData.channel.replace('::$chat', ''); + + // Route based on headers or metadata + if (command === 'translate') { + await handleTranslation(text, headers['x-target-lang'], roomName); + } else if (intent === 'weather-query') { + await handleWeatherQuery(metadata.location, roomName); + } + } +} +``` + + +For complete details on message decoding and structure, see [Extract messages via integrations](/docs/chat/external-storage-and-processing/data-extraction#decoding). + +## Responding back to chat + +After processing messages through your external system, publish responses back to the chat room. This completes the bidirectional integration flow. + +### Avoiding infinite loops + +When your integration responds back to chat, ensure responses don't trigger the same integration rule. Use Ably's [skip integrations](/docs/platform/integrations/skip-integrations) feature to publish messages that bypass integration rules. + +**Note:** Your API key or token must have the [`privileged-headers`](/docs/auth/capabilities#capability-operations) capability to skip integrations. + + +```javascript +const { ChatClient } = require('@ably/chat'); + +async function sendResponseToChat(roomName, responseText) { + // Initialize Chat client with privileged API key + const chatClient = new ChatClient({ key: 'YOUR_API_KEY' }); + + // Get the chat room + const room = await chatClient.rooms.get(roomName); + + // Send response message skipping integration rules + await room.messages.send({ + text: responseText + }, { + extras: { + privileged: { + skipRule: '*' // Skip all integration rules + } + } + }); +} +``` + + +Alternatively, you can skip only specific rules by providing the rule IDs as an array instead of `'*'`. The rule ID is available in the integration webhook envelope's `ruleId` field, or you can find it in your Ably [dashboard](https://ably.com/dashboard) under Integrations. + +## Common processing patterns + +### AI assistant pattern + +Process user commands through AI models and respond with generated content: + + +```javascript +async function handleAIAssistant(text, roomName) { + // Call your AI service + const response = await callAIService(text); + + // Send response back to chat + await sendResponseToChat(roomName, response); +} + +async function callAIService(text) { + // Example: OpenAI, Anthropic, etc. + const response = await fetch('https://api.openai.com/v1/chat/completions', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + model: 'gpt-4', + messages: [{ role: 'user', content: text }] + }) + }); + + const data = await response.json(); + return data.choices[0].message.content; +} +``` + + +### Translation pattern + +Automatically translate messages for multilingual chat rooms: + + +```javascript +async function handleTranslation(text, targetLang, roomName) { + // Call your translation service + const translation = await translateText(text, targetLang); + + // Send translated message back to chat + await sendResponseToChat(roomName, `[${targetLang.toUpperCase()}] ${translation}`); +} + +async function translateText(text, targetLang) { + // Example: Google Translate, DeepL, etc. + const response = await fetch('https://translation.googleapis.com/language/translate/v2', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + q: text, + target: targetLang, + key: process.env.GOOGLE_TRANSLATE_API_KEY + }) + }); + + const data = await response.json(); + return data.data.translations[0].translatedText; +} +``` + + +### Moderation pattern + +Filter messages through moderation services: + + +```javascript +async function handleModeration(text, roomName, messageSerial) { + // Call your moderation service + const moderationResult = await moderateContent(text); + + if (!moderationResult.approved) { + // Delete the original message + await deleteMessage(roomName, messageSerial); + + // Optionally send moderation notice + await sendResponseToChat( + roomName, + '⚠️ A message was removed for violating community guidelines' + ); + } +} + +async function moderateContent(text) { + // Example: OpenAI Moderation API, custom service, etc. + const response = await fetch('https://api.openai.com/v1/moderations', { + method: 'POST', + headers: { + 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ input: text }) + }); + + const data = await response.json(); + return { + approved: !data.results[0].flagged + }; +} +``` + + +### Notification pattern + +Detect mentions and trigger custom notifications: + + +```javascript +async function handleMentions(text, metadata, roomName) { + // Extract mentions from text + const mentions = extractMentions(text); + + if (mentions.length > 0) { + // Send notifications to mentioned users + await Promise.all(mentions.map(userId => + sendNotification(userId, { + type: 'mention', + room: roomName, + text: text, + from: metadata.userId + }) + )); + } +} + +function extractMentions(text) { + // Extract @mentions from text + const mentionRegex = /@(\w+)/g; + const mentions = []; + let match; + + while ((match = mentionRegex.exec(text)) !== null) { + mentions.push(match[1]); + } + + return mentions; +} + +async function sendNotification(userId, notificationData) { + // Send to your notification service + await fetch('https://your-notification-service.com/send', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + userId, + ...notificationData + }) + }); +} +``` + + +## Complete example: Live translation + +The following example demonstrates a complete bidirectional integration where messages are processed, translated, and responses are sent back to the chat. + +### Step 1: Client sends message with metadata + + +```javascript +// Client application +await room.messages.send({ + text: "Hello!", + metadata: { + intent: 'translate', + targetLanguage: 'fr' // User specified target language + } +}); +``` + + +### Step 2: Integration rule forwards to webhook + +Configure an integration rule in your dashboard: +- Channel filter: `^chat:.*` (matches all chat rooms) +- Event type: `channel.message` +- Endpoint: `https://your-domain.com/webhook` + +### Step 3: Webhook processes and responds + + +```javascript +const express = require('express'); +const Ably = require('ably'); +const { ChatClient } = require('@ably/chat'); + +const app = express(); +app.use(express.json()); + +app.post('/webhook', async (req, res) => { + const envelope = req.body; + + // Decode messages + const messages = Ably.Realtime.Message.fromEncodedArray(envelope.messages); + + for (const msg of messages) { + const text = msg.data?.text; + const metadata = msg.data?.metadata || {}; + const roomName = envelope.channel.replace('::$chat', ''); + + // Process based on metadata intent + if (metadata.intent === 'translate') { + const translation = await translateText(text, metadata.targetLanguage); + await sendResponseToChat(roomName, `Translation: ${translation}`); + } + } + + res.status(200).send('OK'); +}); + +async function translateText(text, targetLang) { + // Call your translation service + // This is a placeholder + return `[Translated to ${targetLang}]: ${text}`; +} + +async function sendResponseToChat(roomName, responseText) { + const chatClient = new ChatClient({ key: process.env.ABLY_API_KEY }); + const room = await chatClient.rooms.get(roomName); + + await room.messages.send({ + text: responseText + }, { + extras: { + privileged: { + skipRule: '*' // Prevent infinite loop + } + } + }); +} + +app.listen(3000); +``` + + +### Step 4: Clients receive the response + +All clients subscribed to the room receive the translation in real-time: + + +```javascript +room.messages.subscribe((event) => { + console.log('Received:', event.message.text); + // Output: "Translation: [Translated to fr]: Bonjour!" +}); +``` + + +## Key considerations + +Consider the following when integrating external systems with Ably Chat: + +* **Avoiding infinite loops:** Always use the [skip integrations flag](#responding) when responding back to chat to prevent your response from triggering the integration again +* **Scale and reliability:** Different integration methods offer different reliability guarantees. See [choosing an integration method](#implementation-options) for guidance +* **External system limitations:** Your external service may have rate limits, processing constraints, or availability issues. Implement retry logic, circuit breakers, and caching to handle these limitations gracefully +* **Cost optimization:** Only process messages that require processing. Use channel filters and metadata/headers to route messages efficiently +* **Security:** Always validate and sanitize client-provided metadata before using it in your integration logic +* **Error handling:** Handle external system failures gracefully - consider fallback responses or queuing failed requests for retry + +## Related documentation + +* [Extract messages via integrations](/docs/chat/external-storage-and-processing/data-extraction) - Complete technical guide for setting up webhooks, queues, and streaming +* [Store messages in your database](/docs/chat/external-storage-and-processing/data-storage) - Guide for long-term storage +* [Chat integrations](/docs/chat/integrations) - Technical reference for Chat message structure +* [Platform Integrations](/docs/platform/integrations) - Detailed setup for all integration types +* [Chat SDK documentation](/docs/chat) - Comprehensive guide to Ably Chat features +* [Chat Moderation](/docs/chat/moderation) - Filter and moderate chat content + +For custom integration requirements or questions, [contact Ably support](https://ably.com/support). \ No newline at end of file From 84195a327ee9e1df9d3d585f0199706a21595cd1 Mon Sep 17 00:00:00 2001 From: Steven Lindsay Date: Thu, 18 Dec 2025 15:13:17 +0000 Subject: [PATCH 21/33] Refactor and move data storage portion of the export guide. --- .../data-storage.mdx | 251 ++++++++++++++++++ 1 file changed, 251 insertions(+) create mode 100644 src/pages/docs/chat/external-storage-and-processing/data-storage.mdx diff --git a/src/pages/docs/chat/external-storage-and-processing/data-storage.mdx b/src/pages/docs/chat/external-storage-and-processing/data-storage.mdx new file mode 100644 index 0000000000..518cb39d52 --- /dev/null +++ b/src/pages/docs/chat/external-storage-and-processing/data-storage.mdx @@ -0,0 +1,251 @@ +--- +title: "Store messages in your database" +meta_description: "Store chat messages from Ably Chat in your own database for long-term retention, compliance, and analytics." +meta_keywords: "chat storage, database, data retention, compliance, analytics, message history, chat export" +--- + +Store chat messages from Ably Chat in your own database for long-term retention, compliance, analytics, or to maintain your database as the canonical source of truth. + +While Ably Chat provides flexible data retention for messages (30 days by default, up to a year on request), this page discusses options for longer-term storage and additional control over your chat data. + +## Why store chat data? + +Storing chat data in your own database enables many use cases: + +- **Compliance and legal requirements**: Meet data retention policies, maintain audit trails for support conversations, or fulfill regulatory requirements +- **Analytics and business intelligence**: Build dashboards, train ML models, analyze customer sentiment, or track support quality metrics +- **Enhanced functionality**: Implement features that need chat history, such as full-text search across all messages +- **Single source of truth**: Maintain your database as the canonical source of data +- **Long-term storage**: Store chat data for longer than Ably Chat's retention period + +## Key considerations + +Consider the following when storing chat data: + +### Database schema design + +Design your schema to support the features you need while considering scale and reliability: +- **Database schema**: Design your schema to allow you to easily build the features you need, keeping in mind scale and reliability. +- **Version history requirements**: Decide whether you need to store all versions of messages or just the latest version (see [Decision 1](#decision-1) below) +- **Concurrent writes**: New messages, updates, and deletes will arrive concurrently, so your database must handle this. Consider reducing roundtrips, managing locks, and handling race conditions +- **Indexing strategy**: Plan indexes for efficient queries on room name, timestamp, serial, and version serial +- **Storage optimization**: Consider whether to store full message objects or normalized relational data + +### Scale and reliability + +Depending on your application's scale, consider: + +- **Message ingestion throughput**: How many messages per second will your database need to handle? +- **Consumer scaling**: How will you scale up consumers during peak times? +- **Database performance**: Will your database handle the write volume? Consider write-optimized databases or batch writes +- **Query performance**: How will you efficiently retrieve messages for chat windows or search results? + +### Data latency and consistency + +When using integrations, there will be a small delay between a message being published and it arriving in your database: + +- **Integration delay**: Typically milliseconds to seconds depending on the integration method +- **Database write time**: Additional time to write to your database +- **Eventual consistency**: Accept that your database will be eventually consistent with Ably Chat + +If you need your database to be immediately consistent, consider [publishing via your own servers](#publish-via-own). + +### Messages beyond retention period + +Consider how to retrieve and display messages older than Ably Chat's retention period: + +- **Hydrating chat windows**: Can you efficiently fetch recent messages from both your database and Ably Chat history? +- **Mixed sources**: How will you merge messages from your database with messages from Ably Chat history? +- **UI consistency**: How will you indicate to users which messages came from long-term storage? + +## Implementation approaches + +Choose the approach that fits your requirements: + +### 1. Using integrations (recommended) + +Extract messages via [webhooks, queues, or streaming](/docs/chat/external-storage-and-processing/data-extraction) and save them to your database. This is the recommended approach for most use cases. + +**Benefits:** +- Proven, scalable solution +- Leverage Ably's infrastructure +- Multiple integration methods to choose from +- Monitoring and error handling built-in + +**When to use:** +- You want reliable, continuous ingestion +- You're comfortable with eventual consistency (milliseconds to seconds delay) +- You need to store messages from ongoing or long-running chats + +See the [data extraction guide](/docs/chat/external-storage-and-processing/data-extraction) for complete setup details. + +### 2. Publishing via your own servers + +Proxy message publishing through your own servers to save messages as they're produced. + +**Benefits:** +- Full control over publishing flow +- Immediate consistency (no integration delay) +- Opportunity to add validation or business logic before publishing +- Can use Chat REST API or SDK to publish + +**Considerations:** +- You must handle updates and deletes yourself, including all consistency issues +- Message reactions require using integrations (you won't have access to reaction summaries otherwise) +- Your servers become part of the critical publish path (availability and latency impact) +- Must handle the scale of realtime publishes +- Keeping both systems in sync is complex - need mitigation strategies for all failure scenarios + +**When to use:** +- Your database must be immediately consistent +- You need to validate or transform messages before publishing +- You already proxy messages through your servers for other reasons + +### 3. Using the Chat History endpoint + +Fetch completed chat histories using the [Chat History endpoint](/docs/api/chat-rest#tag/rooms/paths/~1chat~1%7Bversion%7D~1rooms~1%7BroomName%7D~1messages/get) or [Chat SDK](/docs/chat/rooms/history). + +**Benefits:** +- Simple, reliable solution for archival +- No integration setup required +- Perfect for chats with clear start and end times + +**Considerations:** +- Intended for pre-filling chat windows, not continuous ingestion +- Returns only the latest version of each message (not a full changelog) +- Returns messages sorted by `serial` (canonical global order) +- Must decide when to trigger export (closed ticket, ended session, etc.) +- Impractical for long-running chats if you need all updates/deletes (must fetch from start each time) + +**When to use:** +- Archiving completed chats (closed support tickets, ended game sessions) +- Clear start and end boundaries for chats +- Don't need continuous ingestion or full version history + +Use the [`[meta]channel.lifecycle`](/docs/metadata-stats/metadata/subscribe#channel-lifecycle) metachannel to detect when channels close, or use your business logic (ticket closed, session ended) to trigger exports. + +## Storing messages + +After [extracting messages via integrations](/docs/chat/external-storage-and-processing/data-extraction), you need to make decisions about how to store them in your database. + +### Decision 1: Full version history or just the latest version? + +Do you need all versions of a message or just the latest version? + - Messages are uniquely identified by their `serial`. Message versions are identified by the message's `version.serial` property. + - Lexicographically higher `version.serial` means a newer version. + - If you need to store all versions of a message, uniquely index by `roomName`, `serial` and `version.serial`. + - If you only need the latest version of a message, uniquely index by `roomName` and `serial`, and only update if the received `version.serial` is greater than what you have stored. This handles out-of-order delivery. + - When performing a message search or lookup, do you want to return only the latest version of each message, even if you store the full version history? + - If you are looking to hydrate chat windows from your own database, think of how to efficiently retrieve the latest version of each message for a time window. For example, this can be implemented via a separate table or by iterating through all versions and filtering old versions out. + + + ```javascript + const saveMessageVersion = (roomName, message) => { + if (message.action === 'message.summary') { + // summary events are not part of the message version history, so discard + return; +} + + // Pseudo-code: only insert if you don't already have this message version + // Implementation depends on your database's upsert/conflict handling capabilities + await insertIfNotExists(roomName, message.serial, message.version.serial, message); +}; + ``` + + +Read more about [message versioning and sorting](/docs/chat/rooms/messages#ordering-update-delete) in the messages documentation. + +### Decision 2: How to store message reactions? + +If you need to store message reactions you need to consider the following: + +1. Do you need to store only the current state of the reactions, historic snapshots of the current state, or the full history of all individual reactions? + - If you only need the current state (latest summary), simply save the values provided in the latest message with action `message.summary`. Uniquely index by `roomName` and `serial`. + - If you need to store historic snapshots, store all `message.summary` events for every message. Note that when a message receives many reactions in a short amount of time, summaries can be rolled up for cost and bandwidth optimisation, so not every reaction gets a summary event published. +2. Do you have a requirement to store the list of clientIds who reacted to a message, or just the totals? + - If you only need the totals, simply use the values provided in each message with action `message.summary`. + - If you need the list of clientIds who reacted, use the values from reaction summaries. + +If you do not need to store message reactions, you can simply discard them. Never store the `reactions` (or `annotations`) field and ignore messages with action `message.summary`. + +## Using a webhook + +Ably can forward messages to your own system via a webhook. This is the simplest to set up if you don't already have other systems in place for message ingestion. This section covers the simple HTTP endpoint webhook, but the same principles apply to other webhook integrations such as AWS Lambda, Azure Function, Google Function, and others. + +Read the guide on [outbound webhooks](/docs/platform/integrations/webhooks) for more details on how to set up a webhook with Ably for the platform of your choice. + +You need to consider: +- **Redundancy**: In case of failure, Ably will retry delivering the message to your webhook, but only for a short period. You can see errors in the [`[meta]log` channel](/docs/platform/errors#meta). +- **Ordering**: Messages can arrive out-of-order. You can sort them using their `serial` and `version.serial` properties. +- **Consistency**: Webhook calls that fail will lead to inconsistencies between your database and Ably, which can be difficult to resolve. Detect if this happens using the `[meta]log` channel and use the [history endpoint](#history-endpoint) to backfill missing data. +- **[At-least-once delivery](/docs/platform/architecture/idempotency#protocol-support-for-exactly-once-delivery)**: You need to handle duplicate messages. Deduplication can be done by checking `serial` and `version.serial`. + +## Using outbound streaming + +Ably can stream messages directly to your own queueing or streaming service: Kinesis, Kafka, AMQP, SQS. Read the guide on [outbound streaming](/docs/platform/integrations/streaming) for more details on how to set up the streaming integration with Ably for the service of your choice. + +Benefits: +- Use your existing queue system to process and save messages from Ably. +- You control your own queue system, so you have full control over message ingestion from queue to database in terms of retry strategies, retention policies, queue lengths, and so on. + +You need to consider: +- You need to maintain and be responsible for a reliable queue system. If you don't already have such a system, it increases complexity on your end. +- Consistency. If your queue system is not reachable, you will lose messages. Errors can be seen in the [`[meta]log` channel](/docs/platform/errors#meta). + +## Using an Ably queue + +Ably can forward messages from chat room channels to an [Ably Queue](/docs/platform/integrations/queues), which you can then consume from your own servers to save messages to your own database. Read the guide on [Ably queues](/docs/platform/integrations/queues) for more details on how to set up the queue integration with Ably. + +Ably ensures that each message is delivered to only one consumer even if multiple consumers are connected. + +Benefits of using an Ably queue: +- You can consume it from your servers, meaning overall this is fault-tolerant. Ably takes care of the complexity of maintaining a queue. +- You can use multiple queues and configure which channels go to which queue via regex filters on the channel name. +- Fault-tolerant: if your systems suffer any temporary downtime, you will not miss messages, up to the queue max size. There is a deadletter queue to handle the situation where messages are dropped from the Ably Queue. + +You need to consider: +- During peak times you may need to scale up your consumers to avoid overloading the queue past the maximum queue length allowed. +- Each message has a time-to-live in the queue. The default and maximum is 60 minutes. +- Oldest messages are dropped if the maximum queue length is exceeded. Check the [dead letter queue](/docs/platform/integrations/queues#deadletter) to see if this is happening. +- Always consume messages from the [dead letter queue](/docs/platform/integrations/queues#deadletter) to monitor errors. + +## Publishing via your own servers + +Change the publish path: instead of publishing Chat messages, updates, and deletes to Ably directly, proxy them through your own server. This gives you the opportunity to also save the messages as they are produced, and also apply different validation schemes if needed. + +Benefits: +- Full control over publishing. +- Opportunity to add extra validation before publishing to Ably. +- Opportunity to add extra processing or business logic before publishing to Ably. +- You can publish messages directly via the Chat REST API, and avoid having to encode/decode Chat Messages to and from Ably Pub/Sub messages. +- If you are using a supported language, you have the option to publish via the Chat SDK. + +You need to consider: +- You need to handle updates and deletes on your own, including all consistency issues that arise from this. +- Storing message reactions will require using one of the other methods presented in this guide, otherwise you will not have access to the aggregates (summaries) that Ably provides. +- Your own servers are in the middle of the message publish path, so they can become a bottleneck in availability and will add latency in the publish path. +- Your own servers will need to handle the scale you operate at for realtime publishes. +- Keeping both systems in sync can be a difficult problem to solve in all edge cases. Inconsistencies can happen if either publishing to Ably or saving to your own database fails. You will need mitigation strategies to handle all failure scenarios to ensure eventual consistency. + +## Using the Chat History endpoint + +You can fetch the message history of a chat room using the [Chat History endpoint](/docs/api/chat-rest#tag/rooms/paths/~1chat~1%7Bversion%7D~1rooms~1%7BroomName%7D~1messages/get) or the [Chat SDK](/docs/chat/rooms/history). The chat room history endpoint is a paginated HTTP endpoint that allows you to retrieve messages from a chat room. The Chat SDK provides a convenient way to fetch the history of a chat room. + +If your use case is to archive chats that have ended, such as to export the chat history of a support ticket that is closed, you can use the chat history endpoint to export the messages to your own system. Read the docs on [chat history](/docs/chat/rooms/history) for more details. + +The intended use of the chat history endpoint is to retrieve messages for pre-filling a chat window, not for continuous ingestion into other systems. As a result, there are some important things to consider: +- The history endpoint is not a changelog, it is a snapshot of the messages in the room at the time the request is made. It returns only the latest version of each message. +- The history API returns messages in their canonical global order (sorted by `serial`). +- You will need to decide when and which rooms to import messages from. The metachannel [`[meta]channel.lifecycle`](/docs/metadata-stats/metadata/subscribe#channel-lifecycle) provides events when channels are opened and closed, but your business logic might provide a better solution, for example import when a support ticket is closed or game session ends. +- You can import the same room multiple times (deduplicate by `serial` and `version.serial`). However, to capture all updates and deletes, you will need to fetch from the first message each time, which can be impractical for long-running chats with large message histories. + +For use cases where there is a clear start and end of the chat, exporting the chat via history requests is a simple, reliable solution. If there is no clear start and end for chats, if you require continuous ingestion, or if you need the full message version history, please consider using one of the other methods mentioned in this guide. + + +## Related documentation + +* [Extract messages via integrations](/docs/chat/external-storage-and-processing/data-extraction) - Complete setup guide for webhooks, queues, and streaming +* [Chat integrations](/docs/chat/integrations) - Technical reference for message structure mapping +* [Message structure](/docs/chat/rooms/messages#structure) - Chat message format details +* [Chat history](/docs/chat/rooms/history) - Retrieve historical messages via API +* [Process and respond to messages](/docs/chat/external-storage-and-processing/data-processing) - Guide for bidirectional integrations From 216d4cdd1518f4555867d887e30386517f91a481 Mon Sep 17 00:00:00 2001 From: Steven Lindsay Date: Thu, 18 Dec 2025 15:13:45 +0000 Subject: [PATCH 22/33] Add a basic overview of the data handling tree --- .../external-storage-and-processing/index.mdx | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 src/pages/docs/chat/external-storage-and-processing/index.mdx diff --git a/src/pages/docs/chat/external-storage-and-processing/index.mdx b/src/pages/docs/chat/external-storage-and-processing/index.mdx new file mode 100644 index 0000000000..48d169ee78 --- /dev/null +++ b/src/pages/docs/chat/external-storage-and-processing/index.mdx @@ -0,0 +1,73 @@ +--- +title: External storage and processing +meta_description: "Extract, store, and process chat messages from Ably Chat using integrations." +meta_keywords: "chat integrations, message extraction, external storage, message processing, webhooks, streaming, queues" +--- + +Extract chat messages from Ably Chat to external systems for storage, processing, or analysis. This enables you to build features like AI assistants, long-term storage, compliance systems, and more while maintaining realtime message delivery to chat participants. + +Chat rooms are built on Ably Pub/Sub channels, allowing you to leverage the full range of Ably's [platform integrations](/docs/platform/integrations) to forward messages to external systems. + +## Common use cases + +### Process messages with external systems + +Extract and process messages through external systems, then respond back to the chat in real-time: + +* **AI-powered assistance**: Process commands through language models and respond with helpful information +* **Real-time translation**: Automatically translate messages for multilingual chat rooms +* **Content moderation**: Filter messages through moderation services +* **Notifications**: Detect mentions or keywords and trigger custom notification systems +* **Command processing**: Handle slash commands that invoke server-side logic + +[Read the processing guide →](/docs/chat/external-storage-and-processing/data-processing) + +### Store messages in your database + +Export chat messages to your own database for long-term storage and additional capabilities: + +* **Compliance and legal requirements**: Meet data retention policies and regulatory requirements +* **Analytics and business intelligence**: Build dashboards, train ML models, analyze sentiment +* **Enhanced functionality**: Implement features that need chat history, such as full-text search +* **Single source of truth**: Maintain your database as the canonical source +* **Long-term storage**: Store data for longer than Ably Chat's retention period + +[Read the storage guide →](/docs/chat/external-storage-and-processing/data-storage) + +## How it works + +All approaches use Ably's [platform integrations](/docs/platform/integrations) to extract messages from chat rooms: + +1. Configure an integration rule to match specific chat rooms using channel name filters +2. Ably forwards matching messages to your external system via webhook, queue, or streaming +3. Your system decodes and processes the messages +4. Optionally respond back to the chat (for processing use cases) + +## Integration methods + +Choose the integration method that fits your architecture and scale requirements: + +| Method | Best For | Delivery Guarantee | +|--------|----------|-------------------| +| [Webhooks](/docs/platform/integrations/webhooks) | Serverless functions, simple endpoints | At-least-once with limited retry | +| [Ably Queues](/docs/platform/integrations/queues) | Ordered processing, fault-tolerant delivery | At-least-once with dead letter queue | +| [Streaming](/docs/platform/integrations/streaming) | High-throughput, existing infrastructure | Depends on target system | + +[Read the extraction guide →](/docs/chat/external-storage-and-processing/data-extraction) + +## Choosing your approach + +Use this comparison to determine which guides you need: + +| Goal | Use Guide | Key Focus | +|------|-----------|-----------| +| Understand how to extract messages | [Data extraction](/docs/chat/external-storage-and-processing/data-extraction) | Integration setup, decoding, monitoring | +| Store messages in your database | [Data storage](/docs/chat/external-storage-and-processing/data-storage) | Database design, storage decisions, retention | +| Process and respond to messages | [Data processing](/docs/chat/external-storage-and-processing/data-processing) | Bidirectional flow, response patterns, AI/translation | + +## Related documentation + +* [Chat integrations](/docs/chat/integrations) - Technical reference for message structure mapping +* [Platform integrations](/docs/platform/integrations) - Complete integration setup guides +* [Message structure](/docs/chat/rooms/messages#structure) - Chat message format details +* [Chat history](/docs/chat/rooms/history) - Retrieve historical messages via API From 52b194fa7feecf87a60c126e4128fde449cfc4f8 Mon Sep 17 00:00:00 2001 From: Steven Lindsay Date: Thu, 18 Dec 2025 16:10:59 +0000 Subject: [PATCH 23/33] Updating data-extraction page --- .../data-extraction.mdx | 48 ++++++++----------- 1 file changed, 19 insertions(+), 29 deletions(-) diff --git a/src/pages/docs/chat/external-storage-and-processing/data-extraction.mdx b/src/pages/docs/chat/external-storage-and-processing/data-extraction.mdx index 4fc80b6132..0ca4f551a0 100644 --- a/src/pages/docs/chat/external-storage-and-processing/data-extraction.mdx +++ b/src/pages/docs/chat/external-storage-and-processing/data-extraction.mdx @@ -149,17 +149,19 @@ Metadata and headers enable you to control how messages are processed by externa ### Metadata -Message [`metadata`](/docs/chat/rooms/messages#structure) is set by the client when sending a message. Use metadata for business data that should persist with the message. +Message [`metadata`](/docs/chat/rooms/messages#structure) is set by the client when sending a message. Use metadata for storing JSON-serializable structured data relevant to your application logic. + +For example, if triggering some notification process from a particular client, you might include user or intent information in the metadata: ```javascript // Client sends message with metadata await room.messages.send({ - text: 'What is the weather in London?', + text: '@mention User123 "Hey, how are you?"', metadata: { - intent: 'weather-query', + targetClientId: 'User123', location: 'London', - userId: 'user-123' + timestamp: Date.now() } }); ``` @@ -169,16 +171,23 @@ await room.messages.send({ ### Headers -Message [`headers`](/docs/chat/rooms/messages#structure) can be set by the client when sending a message, similar to metadata. However, headers can also be added by integration rules when the rule triggers. +Message [`headers`](/docs/chat/rooms/messages#structure) can be set by the client when sending a message, similar to metadata. However, they are more typically used for [filtering](/docs/pub-sub/advanced#filter-subscribe) subscriptions and routing for integrations. + +For example, you might include headers to indicate the type of processing required by your integration: ```javascript // Client sends message with headers await room.messages.send({ - text: '@bot translate to French', + text: '@mention User123 "Hey, how are you?"', + metadata: { + targetClientId: 'User123', + location: 'London', + timestamp: Date.now() + }, headers: { - 'x-command': 'translate', - 'x-target-lang': 'fr' + 'x-intent': 'notification', + 'x-priority': 'high' } }); ``` @@ -188,29 +197,10 @@ await room.messages.send({ When configuring an integration rule in your Ably dashboard, you can specify headers to be added when the rule triggers. This allows you to attach additional metadata to messages only when they match your integration criteria. -For example, you might add: -- A `x-rule-id` header to identify which rule processed the message -- A `x-processed-timestamp` header to track when the rule was triggered -- A `x-integration-type` header to indicate the processing type (e.g., "translation", "moderation") - -These rule-level headers are added after the client publishes the message, so they appear in the enveloped message received by your integration endpoint but not in the original message stored in chat history. +These rule-level headers are added after the client publishes the message, so they appear in the enveloped message received by your integration endpoint but not in the original message stored in chat history. They will also not be visible to other chat clients. **Important:** While rule-level headers cannot be set or modified by clients, neither headers nor metadata should be treated as fully server-authoritative. Always validate and sanitize all data in your integration code before using it for business logic or security decisions. -### Metadata vs Headers - -Both metadata and headers are persisted with messages and accessible in your integration: - -| | Metadata | Headers | -|---|----------|---------| -| **Set by** | Client only | Client or integration rule | -| **Added when** | On message publish | On message publish (client) or when rule triggers (rule-level) | -| **Best for** | Business data that should persist | Routing logic and integration control | -| **Trusted?** | No (always client-controlled) | No (validate both client and rule-added headers) | -| **Use case** | User IDs, context, intent | Integration routing, action types, rule metadata | - -**Key distinction:** The main advantage of headers over metadata is that headers can be added by integration rules when they trigger, allowing you to attach additional context to messages only when they're being processed by your integration. - ## Message versioning Chat messages have a versioning system for updates and deletes: @@ -601,4 +591,4 @@ Read more about [message reactions](/docs/chat/rooms/message-reactions) and the * [Message structure](/docs/chat/rooms/messages#structure) - Chat message format details * [Chat history](/docs/chat/rooms/history) - Retrieve historical messages via API * [Store messages in your database](/docs/chat/external-storage-and-processing/data-storage) - Guide for long-term storage -* [Process and respond to messages](/docs/chat/external-storage-and-processing/data-processing) - Guide for bidirectional integrations \ No newline at end of file +* [Process and respond to messages](/docs/chat/external-storage-and-processing/data-processing) - Guide for bidirectional integrations From 770303ff17376b48088b901388da8bb682ca23d4 Mon Sep 17 00:00:00 2001 From: Steven Lindsay Date: Thu, 18 Dec 2025 16:32:39 +0000 Subject: [PATCH 24/33] wip --- .../external-storage-and-processing/index.mdx | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/pages/docs/chat/external-storage-and-processing/index.mdx b/src/pages/docs/chat/external-storage-and-processing/index.mdx index 48d169ee78..e415633ce4 100644 --- a/src/pages/docs/chat/external-storage-and-processing/index.mdx +++ b/src/pages/docs/chat/external-storage-and-processing/index.mdx @@ -30,13 +30,13 @@ Export chat messages to your own database for long-term storage and additional c * **Analytics and business intelligence**: Build dashboards, train ML models, analyze sentiment * **Enhanced functionality**: Implement features that need chat history, such as full-text search * **Single source of truth**: Maintain your database as the canonical source -* **Long-term storage**: Store data for longer than Ably Chat's retention period +* **Long-term storage**: Store data for longer than Ably Chat's maximum retention period (365 days) [Read the storage guide →](/docs/chat/external-storage-and-processing/data-storage) ## How it works -All approaches use Ably's [platform integrations](/docs/platform/integrations) to extract messages from chat rooms: +When using Ably's [platform integrations](/docs/platform/integrations) to extract messages from chat rooms: 1. Configure an integration rule to match specific chat rooms using channel name filters 2. Ably forwards matching messages to your external system via webhook, queue, or streaming @@ -53,17 +53,7 @@ Choose the integration method that fits your architecture and scale requirements | [Ably Queues](/docs/platform/integrations/queues) | Ordered processing, fault-tolerant delivery | At-least-once with dead letter queue | | [Streaming](/docs/platform/integrations/streaming) | High-throughput, existing infrastructure | Depends on target system | -[Read the extraction guide →](/docs/chat/external-storage-and-processing/data-extraction) - -## Choosing your approach - -Use this comparison to determine which guides you need: - -| Goal | Use Guide | Key Focus | -|------|-----------|-----------| -| Understand how to extract messages | [Data extraction](/docs/chat/external-storage-and-processing/data-extraction) | Integration setup, decoding, monitoring | -| Store messages in your database | [Data storage](/docs/chat/external-storage-and-processing/data-storage) | Database design, storage decisions, retention | -| Process and respond to messages | [Data processing](/docs/chat/external-storage-and-processing/data-processing) | Bidirectional flow, response patterns, AI/translation | +[Read the extraction guide →](/docs/chat/external-storage-and-processing/data-extraction) to learn how to set up integrations for use with chat rooms. ## Related documentation From 2c7c3866315ff87b86f08e6cd90a340d12359404 Mon Sep 17 00:00:00 2001 From: Steven Lindsay Date: Thu, 18 Dec 2025 17:21:38 +0000 Subject: [PATCH 25/33] clear-up metadata usage --- .../data-extraction.mdx | 8 +- .../data-processing.mdx | 91 ++++++------------- 2 files changed, 34 insertions(+), 65 deletions(-) diff --git a/src/pages/docs/chat/external-storage-and-processing/data-extraction.mdx b/src/pages/docs/chat/external-storage-and-processing/data-extraction.mdx index 0ca4f551a0..e3d4b92681 100644 --- a/src/pages/docs/chat/external-storage-and-processing/data-extraction.mdx +++ b/src/pages/docs/chat/external-storage-and-processing/data-extraction.mdx @@ -157,9 +157,10 @@ For example, if triggering some notification process from a particular client, y ```javascript // Client sends message with metadata await room.messages.send({ - text: '@mention User123 "Hey, how are you?"', + text: '@john.123 Hey, how are you?', metadata: { targetClientId: 'User123', + type: 'mention', location: 'London', timestamp: Date.now() } @@ -179,9 +180,10 @@ For example, you might include headers to indicate the type of processing requir ```javascript // Client sends message with headers await room.messages.send({ - text: '@mention User123 "Hey, how are you?"', + text: '@john.123 Hey, how are you?', metadata: { - targetClientId: 'User123', + targetClientId: 'john.123', + type: 'mention location: 'London', timestamp: Date.now() }, diff --git a/src/pages/docs/chat/external-storage-and-processing/data-processing.mdx b/src/pages/docs/chat/external-storage-and-processing/data-processing.mdx index cebd4a172e..91603edcd2 100644 --- a/src/pages/docs/chat/external-storage-and-processing/data-processing.mdx +++ b/src/pages/docs/chat/external-storage-and-processing/data-processing.mdx @@ -69,73 +69,40 @@ Ably provides three integration methods, each suited to different use cases. All **Learn more:** See [Extract messages via integrations](/docs/chat/external-storage-and-processing/data-extraction#streaming) for setup details and code examples. -## Using metadata and headers for routing +## Using metadata for structured content -Metadata and headers enable you to control how messages are processed by external systems and add context for integration logic. - -### Metadata - -Message [`metadata`](/docs/chat/rooms/messages#structure) is set by the client when sending a message. Use metadata for business data that should persist with the message. +Clients can provide context for your external services to use while processing by adding data in the [`metadata`](/docs/chat/external-storage-and-processing/data-extraction#metadata-headers) field. For example, when implementing a notification system, you might include the target user ID and notification type: ```javascript // Client sends message with metadata await room.messages.send({ - text: 'What is the weather in London?', + text: '@john.123 Can you review this?', metadata: { - intent: 'weather-query', - location: 'London', - userId: 'user-123' + targetClientId: 'john.123', + notificationType: 'mention' } }); ``` -**Important:** Metadata is not server-validated. Always treat it as untrusted user input in your integration code. - -### Headers - -Message [`headers`](/docs/chat/rooms/messages#structure) can be set by the client when sending a message, similar to metadata. However, headers can also be added by integration rules when the rule triggers. +You can then [extract](/docs/src/pages/docs/chat/external-storage-and-processing/data-processing.mdx#decoding) the metadata in your integration to trigger business logic: ```javascript -// Client sends message with headers -await room.messages.send({ - text: '@bot translate to French', - headers: { - 'x-command': 'translate', - 'x-target-lang': 'fr' - } -}); +// use metadata in your integration to trigger notifications +const metadata = msg.data?.metadata || {}; +if (metadata.notificationType === 'mention') { + await sendNotification(metadata.targetClientId, { + type: 'mention', + text: msg.data.text, + roomName: roomName + }); +} ``` -#### Adding headers at the rule level - -When configuring an integration rule in your Ably dashboard, you can specify headers to be added when the rule triggers. This allows you to attach additional metadata to messages only when they match your integration criteria. - -For example, you might add: -- A `x-rule-id` header to identify which rule processed the message -- A `x-processed-timestamp` header to track when the rule was triggered -- A `x-integration-type` header to indicate the processing type (e.g., "translation", "moderation") - -These rule-level headers are added after the client publishes the message, so they appear in the enveloped message received by your integration endpoint but not in the original message stored in chat history. - -**Important:** While rule-level headers cannot be set or modified by clients, neither headers nor metadata should be treated as fully server-authoritative. Always validate and sanitize all data in your integration code before using it for business logic or security decisions. - -### Metadata vs Headers - -Both metadata and headers are persisted with messages and accessible in your integration: - -| | Metadata | Headers | -|---|----------|---------| -| **Set by** | Client only | Client or integration rule | -| **Added when** | On message publish | On message publish (client) or when rule triggers (rule-level) | -| **Best for** | Business data that should persist | Routing logic and integration control | -| **Trusted?** | No (always client-controlled) | No (validate both client and rule-added headers) | -| **Use case** | User IDs, context, intent | Integration routing, action types, rule metadata | - -**Key distinction:** The main advantage of headers over metadata is that headers can be added by integration rules when they trigger, allowing you to attach additional context to messages only when they're being processed by your integration. +**Important:** Metadata is not server-validated. Always treat it as untrusted user input and validate it in your integration code. ## Processing messages in your integration @@ -225,7 +192,7 @@ Process user commands through AI models and respond with generated content: async function handleAIAssistant(text, roomName) { // Call your AI service const response = await callAIService(text); - + // Send response back to chat await sendResponseToChat(roomName, response); } @@ -243,7 +210,7 @@ async function callAIService(text) { messages: [{ role: 'user', content: text }] }) }); - + const data = await response.json(); return data.choices[0].message.content; } @@ -259,7 +226,7 @@ Automatically translate messages for multilingual chat rooms: async function handleTranslation(text, targetLang, roomName) { // Call your translation service const translation = await translateText(text, targetLang); - + // Send translated message back to chat await sendResponseToChat(roomName, `[${targetLang.toUpperCase()}] ${translation}`); } @@ -275,7 +242,7 @@ async function translateText(text, targetLang) { key: process.env.GOOGLE_TRANSLATE_API_KEY }) }); - + const data = await response.json(); return data.data.translations[0].translatedText; } @@ -291,14 +258,14 @@ Filter messages through moderation services: async function handleModeration(text, roomName, messageSerial) { // Call your moderation service const moderationResult = await moderateContent(text); - + if (!moderationResult.approved) { // Delete the original message await deleteMessage(roomName, messageSerial); - + // Optionally send moderation notice await sendResponseToChat( - roomName, + roomName, '⚠️ A message was removed for violating community guidelines' ); } @@ -314,7 +281,7 @@ async function moderateContent(text) { }, body: JSON.stringify({ input: text }) }); - + const data = await response.json(); return { approved: !data.results[0].flagged @@ -332,10 +299,10 @@ Detect mentions and trigger custom notifications: async function handleMentions(text, metadata, roomName) { // Extract mentions from text const mentions = extractMentions(text); - + if (mentions.length > 0) { // Send notifications to mentioned users - await Promise.all(mentions.map(userId => + await Promise.all(mentions.map(userId => sendNotification(userId, { type: 'mention', room: roomName, @@ -351,11 +318,11 @@ function extractMentions(text) { const mentionRegex = /@(\w+)/g; const mentions = []; let match; - + while ((match = mentionRegex.exec(text)) !== null) { mentions.push(match[1]); } - + return mentions; } @@ -489,4 +456,4 @@ Consider the following when integrating external systems with Ably Chat: * [Chat SDK documentation](/docs/chat) - Comprehensive guide to Ably Chat features * [Chat Moderation](/docs/chat/moderation) - Filter and moderate chat content -For custom integration requirements or questions, [contact Ably support](https://ably.com/support). \ No newline at end of file +For custom integration requirements or questions, [contact Ably support](https://ably.com/support). From 27e5b1526e5d6426afa015ce91bae2c9857b4cdf Mon Sep 17 00:00:00 2001 From: Steven Lindsay Date: Thu, 18 Dec 2025 20:26:18 +0000 Subject: [PATCH 26/33] Reword message ordering and versioning in the data-extraction guide --- src/data/nav/chat.ts | 4 + .../data-extraction.mdx | 85 +++++++------------ .../external-storage-and-processing/index.mdx | 3 +- 3 files changed, 37 insertions(+), 55 deletions(-) diff --git a/src/data/nav/chat.ts b/src/data/nav/chat.ts index 73f58401e8..cab85bd027 100644 --- a/src/data/nav/chat.ts +++ b/src/data/nav/chat.ts @@ -130,6 +130,10 @@ export default { name: 'Data extraction', link: '/docs/chat/external-storage-and-processing/data-extraction', }, + { + name: 'Data processing', + link: '/docs/chat/external-storage-and-processing/data-processing', + }, { name: 'Data storage', link: '/docs/chat/external-storage-and-processing/data-storage', diff --git a/src/pages/docs/chat/external-storage-and-processing/data-extraction.mdx b/src/pages/docs/chat/external-storage-and-processing/data-extraction.mdx index e3d4b92681..dae579a99d 100644 --- a/src/pages/docs/chat/external-storage-and-processing/data-extraction.mdx +++ b/src/pages/docs/chat/external-storage-and-processing/data-extraction.mdx @@ -20,7 +20,7 @@ Each method offers different trade-offs in terms of simplicity, reliability, and ## Filtering rooms -Control which chat rooms trigger integrations using channel name filters. +Integration rules can be configured to match and forward messages from specific chat rooms based on channel name patterns. This enables you to configure any number of integration rules for different use cases and apply them to relevant rooms. ### Setting up filters @@ -30,17 +30,13 @@ When configuring an integration rule in your Ably dashboard: * **Event type**: Use `channel.message` to forward chat messages and exclude presence events * **Enveloped messages**: Enable to receive full message metadata including `serial`, `version`, and `headers` -**Note**: Chat rooms use the `::$chat` suffix on channel names internally. Integration filters match against the full channel name, but you don't need to include the suffix in your filter pattern. - ### Example filter configuration ```javascript // Room name: support:ticket-123 -// Internal channel: support:ticket-123::$chat // Filter pattern: ^support:.* -// Result: Messages from this room will be forwarded - +// Result: Messages from support:ticket-123 will be forwarded const supportRoom = await chatClient.rooms.get('support:ticket-123'); await supportRoom.messages.send({ text: 'Help needed' }); // Will trigger integration ``` @@ -71,11 +67,12 @@ await generalRoom.messages.send({ text: 'Hi' }); // Won't trigger if filter is ^ ## Decoding messages -Messages received through integrations are encoded as Ably Pub/Sub messages and need to be decoded into Chat messages. Full details on the mapping are available in the [Chat integrations](/docs/chat/integrations) documentation. +Messages received through integrations are encoded as Ably Pub/Sub messages and need to be decoded into Chat messages. The Ably-js SDK exposes [functions](#decode-data) you can use to handle this, or you can see the page on [chat integrations](/docs/chat/integrations) for more details on how to manually decode messages. +By default, these messages are sent in an envelope containing additional structured metadata, such as the channel name, app ID, and rule ID. ### Understanding enveloped messages -With enveloping enabled (recommended), messages arrive wrapped in metadata: +With enveloping enabled (recommended), messages arrive wrapped in useful metadata: ```javascript @@ -105,9 +102,9 @@ With enveloping enabled (recommended), messages arrive wrapped in metadata: ``` -### Extracting room name +### Extracting the room name -Remove the `::$chat` suffix to get the room name: +The enveloped payload contains the underlying channel name from which the contained messages originated. To get the corresponding room name, remove the `::$chat` suffix from the string value of the `channel` field: ```javascript @@ -118,9 +115,11 @@ function extractRoomName(channelName) { ``` +This room name can then be used to interact with the Chat SDK and REST API as needed. + ### Decoding message data -Use the Ably SDK to decode messages from the envelope: +You can use the Ably SDK to decode messages from the envelope: ```javascript @@ -138,6 +137,7 @@ function decodeMessages(envelopeData) { timestamp: msg.timestamp, action: msg.action, versionSerial: msg.version?.serial || msg.serial + versionTimestamp: msg.version?.timestamp || msg.timestamp })); } ``` @@ -162,7 +162,7 @@ await room.messages.send({ targetClientId: 'User123', type: 'mention', location: 'London', - timestamp: Date.now() + language: 'en', } }); ``` @@ -201,58 +201,35 @@ When configuring an integration rule in your Ably dashboard, you can specify hea These rule-level headers are added after the client publishes the message, so they appear in the enveloped message received by your integration endpoint but not in the original message stored in chat history. They will also not be visible to other chat clients. -**Important:** While rule-level headers cannot be set or modified by clients, neither headers nor metadata should be treated as fully server-authoritative. Always validate and sanitize all data in your integration code before using it for business logic or security decisions. +**Important:** Carefully validate and sanitize all data in your integration code before using it for business logic or security decisions. -## Message versioning +## Message ordering and versioning -Chat messages have a versioning system for updates and deletes: +Chat message `serial` and `version.serial` fields are [globally unique and lexicographically sortable](/docs/chat/rooms/messages#global-ordering) strings that enable you to correctly order chat messages even when they arrive out of sequence. +Together with the `action` field, these properties allow you to handle message creation, updates, and deletions in the correct order. -* **`serial`**: Unique identifier for the original message -* **`version.serial`**: Identifier for the specific message version -* **`action`**: Indicates message type (create, update, delete, or summary for reactions) +* **`serial`**: Unique identifier for the original message (remains constant across all versions) +* **`version.serial`**: Identifier for a specific message version (only populated for updated or deleted messages) +* **`action`**: Indicates message type (`message.created`, `message.updated`, `message.deleted`, or `message.summary` for reactions) -### Handling message versions +When receiving messages through integrations, `version.serial` will only be present if the message has been updated or deleted (i.e., `action` is not `message.created`). - -```javascript -function processMessage(message) { - // Discard reaction summaries if not needed - if (message.action === 'message.summary') { - return; - } +### Determining message order - // Process based on action - switch (message.action) { - case 'message.created': - handleNewMessage(message); - break; - case 'message.updated': - handleMessageUpdate(message); - break; - case 'message.deleted': - handleMessageDelete(message); - break; - } -} -``` - +Use `serial` and `version.serial` fields to determine the correct order of messages and apply updates or deletions appropriately: -### Version ordering +* **Comparing different messages**: Compare `serial` fields to determine which message was sent first. A lexicographically higher `serial` value indicates a newer message. +* **Comparing versions of the same message**: When both messages have the same `serial` (indicating different versions of the same message), compare `version.serial` values to determine which version is newer. A lexicographically higher `version.serial` indicates a more recent update or delete operation. -Version serials are lexicographically ordered. A higher `version.serial` indicates a newer version: +### Handling out-of-order delivery - -```javascript -function isNewerVersion(existingVersion, newVersion) { - return newVersion > existingVersion; -} +Messages may arrive out of order due to network conditions, retry logic, or when consuming from multiple integration sources. To handle this correctly: -// Only process if this is a newer version -if (isNewerVersion(stored.version.serial, incoming.version.serial)) { - updateMessage(incoming); -} -``` - +1. Check if `message.serial === storedMessage.serial` to identify if they're the same message +2. If the serials match, compare `message.version.serial > storedMessage.version.serial` to see if the incoming version is newer +3. Only process the update or delete if the incoming version is newer than what you have stored + +This approach ensures you always maintain the most recent version of each message, regardless of delivery order. Read more about [message versioning and sorting](/docs/chat/rooms/messages#ordering-update-delete) in the messages documentation. diff --git a/src/pages/docs/chat/external-storage-and-processing/index.mdx b/src/pages/docs/chat/external-storage-and-processing/index.mdx index e415633ce4..d48754bfb4 100644 --- a/src/pages/docs/chat/external-storage-and-processing/index.mdx +++ b/src/pages/docs/chat/external-storage-and-processing/index.mdx @@ -4,7 +4,7 @@ meta_description: "Extract, store, and process chat messages from Ably Chat usin meta_keywords: "chat integrations, message extraction, external storage, message processing, webhooks, streaming, queues" --- -Extract chat messages from Ably Chat to external systems for storage, processing, or analysis. This enables you to build features like AI assistants, long-term storage, compliance systems, and more while maintaining realtime message delivery to chat participants. +Ably Chat messages can be extracted chat to external systems for storage, processing, or analysis. This enables you to implement features like AI assistants, long-term storage, compliance systems, and more while maintaining realtime message delivery to chat participants. Chat rooms are built on Ably Pub/Sub channels, allowing you to leverage the full range of Ably's [platform integrations](/docs/platform/integrations) to forward messages to external systems. @@ -19,6 +19,7 @@ Extract and process messages through external systems, then respond back to the * **Content moderation**: Filter messages through moderation services * **Notifications**: Detect mentions or keywords and trigger custom notification systems * **Command processing**: Handle slash commands that invoke server-side logic +* **Data analysis**: Perform sentiment analysis or topic detection on chat messages [Read the processing guide →](/docs/chat/external-storage-and-processing/data-processing) From 9ef52751f33753b40bee7620c1d01b21ef31ce25 Mon Sep 17 00:00:00 2001 From: Steven Lindsay Date: Thu, 18 Dec 2025 21:23:34 +0000 Subject: [PATCH 27/33] Rework language and sentiment analysis examples in the data-processing guide --- .../data-processing.mdx | 176 +----------------- 1 file changed, 7 insertions(+), 169 deletions(-) diff --git a/src/pages/docs/chat/external-storage-and-processing/data-processing.mdx b/src/pages/docs/chat/external-storage-and-processing/data-processing.mdx index 91603edcd2..29f8f44dde 100644 --- a/src/pages/docs/chat/external-storage-and-processing/data-processing.mdx +++ b/src/pages/docs/chat/external-storage-and-processing/data-processing.mdx @@ -4,9 +4,7 @@ meta_description: "Process chat messages through external systems and respond ba meta_keywords: "chat processing, AI chat, translation, bidirectional integration, message processing, chat automation" --- -Process chat messages through external systems like AI assistants, translation services, or moderation tools, then respond back to the chat in real-time. This bidirectional integration pattern enables powerful features like chatbots, automated support, real-time translation, and more. - -This guide explains how to architect integrations that process messages and respond back to chat, completing the bidirectional flow. +You can process chat messages through external systems like AI tools, translation services, or analytics, then respond back to the chat in real-time. This bidirectional integration pattern enables powerful features like chatbots, automated support, real-time translation, sentiment analysis, and more. ## Why process messages with external systems? @@ -14,7 +12,7 @@ Integrating external systems with Ably Chat enables you to extend your chat appl * **AI-powered assistance**: Process commands or questions through language models and respond with helpful information * **Real-time translation**: Automatically translate messages for multilingual chat rooms -* **Content moderation**: Filter messages through moderation services before they appear in chat +* **Sentiment analysis**: Analyze the emotional tone of messages to gauge user or group sentiment * **Notifications**: Detect mentions or keywords and trigger custom notification systems * **Workflow automation**: Trigger external business processes based on chat activity * **Command processing**: Handle slash commands that invoke server-side logic @@ -23,7 +21,7 @@ Integrating external systems with Ably Chat enables you to extend your chat appl The typical bidirectional integration flow works as follows: -1. A user sends a message to a chat room, optionally including [metadata or headers](#metadata-headers) to control processing +1. A user sends a message to a chat room, optionally including [metadata or headers](#metadata-headers) for processing context 2. An integration rule evaluates the message based on room name pattern matching 3. If the rule matches, Ably forwards the message to your external system 4. Your external system processes the message (AI inference, translation, business logic, etc.) @@ -34,7 +32,7 @@ This bidirectional flow enables powerful patterns like chatbots, automated suppo ## Choosing an integration method -Ably provides three integration methods, each suited to different use cases. All three support the bidirectional pattern described in this guide. +Ably provides several different integration methods, each suited to different use cases. All integrations discussed below can be used to support this bidirectional pattern. ### Webhooks @@ -142,11 +140,11 @@ async function processIntegrationPayload(envelopeData) { ``` -For complete details on message decoding and structure, see [Extract messages via integrations](/docs/chat/external-storage-and-processing/data-extraction#decoding). +For complete details on message decoding and structure, see the section on [extracting messages via integrations](/docs/chat/external-storage-and-processing/data-extraction#decoding). ## Responding back to chat -After processing messages through your external system, publish responses back to the chat room. This completes the bidirectional integration flow. +After processing messages through your external system, you can then publish responses back to the chat room. ### Avoiding infinite loops @@ -181,168 +179,9 @@ async function sendResponseToChat(roomName, responseText) { Alternatively, you can skip only specific rules by providing the rule IDs as an array instead of `'*'`. The rule ID is available in the integration webhook envelope's `ruleId` field, or you can find it in your Ably [dashboard](https://ably.com/dashboard) under Integrations. -## Common processing patterns - -### AI assistant pattern - -Process user commands through AI models and respond with generated content: - - -```javascript -async function handleAIAssistant(text, roomName) { - // Call your AI service - const response = await callAIService(text); - - // Send response back to chat - await sendResponseToChat(roomName, response); -} - -async function callAIService(text) { - // Example: OpenAI, Anthropic, etc. - const response = await fetch('https://api.openai.com/v1/chat/completions', { - method: 'POST', - headers: { - 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - model: 'gpt-4', - messages: [{ role: 'user', content: text }] - }) - }); - - const data = await response.json(); - return data.choices[0].message.content; -} -``` - - -### Translation pattern - -Automatically translate messages for multilingual chat rooms: - - -```javascript -async function handleTranslation(text, targetLang, roomName) { - // Call your translation service - const translation = await translateText(text, targetLang); - - // Send translated message back to chat - await sendResponseToChat(roomName, `[${targetLang.toUpperCase()}] ${translation}`); -} - -async function translateText(text, targetLang) { - // Example: Google Translate, DeepL, etc. - const response = await fetch('https://translation.googleapis.com/language/translate/v2', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - q: text, - target: targetLang, - key: process.env.GOOGLE_TRANSLATE_API_KEY - }) - }); - - const data = await response.json(); - return data.data.translations[0].translatedText; -} -``` - - -### Moderation pattern - -Filter messages through moderation services: - - -```javascript -async function handleModeration(text, roomName, messageSerial) { - // Call your moderation service - const moderationResult = await moderateContent(text); - - if (!moderationResult.approved) { - // Delete the original message - await deleteMessage(roomName, messageSerial); - - // Optionally send moderation notice - await sendResponseToChat( - roomName, - '⚠️ A message was removed for violating community guidelines' - ); - } -} - -async function moderateContent(text) { - // Example: OpenAI Moderation API, custom service, etc. - const response = await fetch('https://api.openai.com/v1/moderations', { - method: 'POST', - headers: { - 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ input: text }) - }); - - const data = await response.json(); - return { - approved: !data.results[0].flagged - }; -} -``` - - -### Notification pattern - -Detect mentions and trigger custom notifications: - - -```javascript -async function handleMentions(text, metadata, roomName) { - // Extract mentions from text - const mentions = extractMentions(text); - - if (mentions.length > 0) { - // Send notifications to mentioned users - await Promise.all(mentions.map(userId => - sendNotification(userId, { - type: 'mention', - room: roomName, - text: text, - from: metadata.userId - }) - )); - } -} - -function extractMentions(text) { - // Extract @mentions from text - const mentionRegex = /@(\w+)/g; - const mentions = []; - let match; - - while ((match = mentionRegex.exec(text)) !== null) { - mentions.push(match[1]); - } - - return mentions; -} - -async function sendNotification(userId, notificationData) { - // Send to your notification service - await fetch('https://your-notification-service.com/send', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - userId, - ...notificationData - }) - }); -} -``` - - ## Complete example: Live translation -The following example demonstrates a complete bidirectional integration where messages are processed, translated, and responses are sent back to the chat. +The following example demonstrates an integration where messages are processed, translated, and responses are sent back to the chat. ### Step 1: Client sends message with metadata @@ -400,7 +239,6 @@ app.post('/webhook', async (req, res) => { async function translateText(text, targetLang) { // Call your translation service - // This is a placeholder return `[Translated to ${targetLang}]: ${text}`; } From cacee0f418d147c5fd12ec30a1e0316d0cc3a52e Mon Sep 17 00:00:00 2001 From: Steven Lindsay Date: Thu, 18 Dec 2025 21:25:48 +0000 Subject: [PATCH 28/33] Removing old integrations guide --- src/data/nav/chat.ts | 4 - .../guides/chat/external-integrations.mdx | 366 ------------------ 2 files changed, 370 deletions(-) delete mode 100644 src/pages/docs/guides/chat/external-integrations.mdx diff --git a/src/data/nav/chat.ts b/src/data/nav/chat.ts index cab85bd027..11bbe4ddd5 100644 --- a/src/data/nav/chat.ts +++ b/src/data/nav/chat.ts @@ -226,10 +226,6 @@ export default { name: 'Export chat messages', link: '/docs/guides/chat/export-chat', }, - { - name: 'Integrate external services', - link: '/docs/guides/chat/external-integrations', - }, ], }, ], diff --git a/src/pages/docs/guides/chat/external-integrations.mdx b/src/pages/docs/guides/chat/external-integrations.mdx deleted file mode 100644 index 741948ae0b..0000000000 --- a/src/pages/docs/guides/chat/external-integrations.mdx +++ /dev/null @@ -1,366 +0,0 @@ ---- -title: "Guide: Integrate external systems with Ably Chat" -meta_description: "Extend Ably Chat with external integrations: process messages with AI, webhooks, and custom logic at scale." -meta_keywords: "chat integrations, external systems, webhooks, AI chat, message processing, chat automation, integration rules, message routing" ---- - -Ably Chat is designed for extensibility. Whether you need to process messages with AI, translate content in real-time, trigger business workflows, or enrich messages with external data, Ably's integration capabilities enable you to extend your chat application without compromising on scale, reliability, or performance. - -This guide explains how to architect integrations that process chat messages through external systems and respond back to the chat in real-time. - -## Why integrate external systems? - -Integrating external systems with Ably Chat enables you to extend your chat application beyond simple message delivery. Common integration use cases include: - -* **AI-powered assistance:** Process commands or questions through language models and respond with helpful information -* **Real-time translation:** Automatically translate messages for multilingual chat rooms -* **Notifications:** Detect and process mentions, or trigger custom notification services -* **Analytics and monitoring:** Analyze sentiment, track engagement metrics, or identify trending topics -* **Workflow automation:** Trigger external business processes based on chat activity -* **Command processing:** Handle slash commands that invoke server-side logic - -## Why Ably for external integrations? - -Ably Chat integrations are built on Ably's proven platform architecture: - -* **Reliable message delivery:** Every integration benefits from Ably's [four pillars of dependability](/docs/platform/architecture): Performance, Integrity, Reliability, and Availability -* **Flexible routing:** Use channel filters to selectively route only the messages that need processing -* **Multiple integration methods:** Choose webhooks, queues, or streaming based on your architecture and scale requirements -* **Proven at scale:** Ably processes over 500 million messages per day with [serverless scalability](/docs/platform/architecture/platform-scalability) -* **No vendor lock-in:** Integrate with any external service using any language or platform - -Because Ably Chat rooms use Pub/Sub channels as their underlying transport, you can leverage all of Ably's [platform integrations](/docs/platform/integrations) to extend your chat application. - -## Understanding the integration flow - -The typical integration flow works as follows: - -1. A user sends a message to a chat room, optionally including [metadata or headers](#metadata-headers) to control processing -2. An integration rule evaluates the message based on room name pattern matching -3. If the rule matches, Ably forwards the message to your external system -4. Your external system processes the message (AI inference, translation, business logic, etc.) -5. The external system [sends a response](#responding) back to the chat room -6. All subscribed clients receive the response in real-time - -This bidirectional flow enables powerful patterns like chatbots, automated support, real-time translation, and more. - -## Choosing an integration method - -Ably provides three integration methods, each suited to different use cases: - -### Webhooks - -[Outbound webhooks](/docs/platform/integrations/webhooks) are ideal for simple business logic and event-driven actions. - -**Best for:** -* Processing @mentions or triggering custom notifications -* Executing lightweight business logic (lookup, validation, etc.) -* Serverless architectures (AWS Lambda, Cloud Functions, etc.) - -**Learn more:** See [Extract messages via integrations](/docs/chat/rooms/message-extraction#webhooks) for setup details and code examples. - -### Ably Queues - -[Ably Queues](/docs/platform/integrations/queues) provide strict ordering and fault-tolerant delivery. - -**Best for:** -* Automated support flows where message order matters -* Processing sequential events (game moves, transaction steps) -* Fault-tolerant message processing with automatic retries - -**Learn more:** See [Extract messages via integrations](/docs/chat/rooms/message-extraction#queues) for setup details and code examples. - -### Streaming - -[Outbound streaming](/docs/platform/integrations/streaming) enables massive scale and integration with existing infrastructure. - -**Best for:** -* High-throughput scenarios (large-scale chats, millions of messages) -* Existing streaming infrastructure (Kafka, Kinesis, etc.) -* Complex data pipelines and analytics - -**Learn more:** See [Extract messages via integrations](/docs/chat/rooms/message-extraction#streaming) for setup details and code examples. - -## Using metadata and headers - -Metadata and headers enable you to control how messages are processed by external systems and add context for integration logic. - -### Metadata - -Message [`metadata`](/docs/chat/rooms/messages#structure) is set by the client when sending a message. Use metadata for business data that should persist with the message. - - -```javascript -// Client sends message with metadata -await room.messages.send({ - text: '@mention userId-123, how are you today?', - metadata: { - intent: 'mention', - userId: 'user-123' - } -}); -``` - - -**Important:** Metadata is not server-validated. Always treat it as untrusted user input in your integration code. - -### Headers - -Message [`headers`](/docs/chat/rooms/messages#structure) can be set by the client when sending a message, similar to metadata. However, headers can also be added by integration rules when the rule triggers. - - -```javascript -// Client sends message with headers -await room.messages.send({ - text: '@bot translate "Hello, nice to meet you!"', - headers: { - 'x-command': 'translate', - 'x-target-lang': 'fr' - } -}); -``` - - -#### Adding headers at the rule level - -When configuring an integration rule in your Ably dashboard, you can specify headers to be added when the rule triggers. This allows you to attach additional metadata to messages only when they match your integration criteria. - -For example, you might add: -- A `x-integration-type` header to indicate the processing type (e.g., "translation", "moderation") - -These rule-level headers are added after the client publishes the message, so they appear in the enveloped message received by your integration endpoint but not in the original message stored in chat history. - -**Important:** While rule-level headers cannot be set or modified by clients, neither headers nor metadata should be treated as fully server-authoritative. Always validate and sanitize all data in your integration code before using it for business logic or security decisions. - -### Metadata vs Headers - -Both metadata and headers are persisted with messages and accessible in your integration: - -| | Metadata | Headers | -|---|----------|---------| -| **Set by** | Client only | Client or integration rule | -| **Added when** | On message publish | On message publish (client) or when rule triggers (rule-level) | -| **Best for** | Business data that should persist | Routing logic and integration control | -| **Trusted?** | No (always client-controlled) | No (validate both client and rule-added headers) | -| **Use case** | User IDs, context, intent | Integration routing, action types, rule metadata | - -**Key distinction:** The main advantage of headers over metadata is that headers can be added by integration rules when they trigger, allowing you to attach additional context to messages only when they're being processed by your integration. - -## Understanding channel names and room names - -Chat rooms are underpinned by Ably Pub/Sub channels with a `::$chat` suffix added to form the full channel name. When using the Chat SDK to create or get a room, this is done automatically - you don't need to include the suffix yourself. - -Integration rules match against the full channel name, but you don't need to include the `::$chat` suffix in your filter pattern. - - -```javascript -// Get a chat room - the room name becomes the channel name with ::$chat suffix -const supportRoom = await chatClient.rooms.get('chat:support'); -// Underlying channel: chat:support::$chat - -// Messages sent to these rooms will trigger an integration -// if your rule's channel filter is: ^chat:support.* -await supportRoom.messages.send({ text: 'Need help' }); - -// Messages sent to other channel patterns will NOT trigger the integration -const generalRoom = await chatClient.rooms.get('chat:general'); -// Underlying channel: chat:general::$chat -await generalRoom.messages.send({ text: 'Hi' }); // Won't trigger if filter is ^chat:support.* -``` - - -## Processing messages in your integration - -When your integration receives messages, extract the metadata and headers to control processing logic. - - -```javascript -const Ably = require('ably'); - -async function processIntegrationPayload(envelopeData) { - // Decode messages using Ably SDK - const decodedMessages = Ably.Realtime.Message.fromEncodedArray(envelopeData.messages); - - for (const msg of decodedMessages) { - // Extract text content - const text = msg.data?.text; - - // Extract headers for routing - const headers = msg.extras?.headers || {}; - const command = headers['x-command']; - - // Extract metadata for business context (treat as untrusted) - const metadata = msg.data?.metadata || {}; - const intent = metadata.intent; - - // Extract room name for sending responses - const roomName = envelopeData.channel.replace('::$chat', ''); - - // Route based on headers or metadata - if (command === 'translate') { - await handleTranslation(text, headers['x-target-lang'], roomName); - } else if (intent === 'weather-query') { - await handleWeatherQuery(metadata.location, roomName); - } - } -} -``` - - -For complete details on message decoding and structure, see [Extract messages via integrations](/docs/chat/rooms/message-extraction#decoding). - -## Responding back to chat - -After processing messages through your external system, publish responses back to the chat room. This completes the bidirectional integration flow. - -### Avoiding infinite loops - -When your integration responds back to chat, ensure responses don't trigger the same integration rule. Use Ably's [skip integrations](/docs/platform/integrations/skip-integrations) feature to publish messages that bypass integration rules. - -**Note:** Your API key or token must have the [`privileged-headers`](/docs/auth/capabilities#capability-operations) capability to skip integrations. - - -```javascript -const { ChatClient } = require('@ably/chat'); - -async function sendResponseToChat(roomName, responseText) { - // Initialize Chat client with privileged API key - const chatClient = new ChatClient({ key: 'YOUR_API_KEY' }); - - // Get the chat room - const room = await chatClient.rooms.get(roomName); - - // Send response message skipping integration rules - await room.messages.send({ - text: responseText - }, { - extras: { - privileged: { - skipRule: '*' // Skip all integration rules - } - } - }); -} -``` - - -Alternatively, you can skip only specific rules by providing the rule IDs as an array instead of `'*'`. The rule ID is available in the integration webhook envelope's `ruleId` field, or you can find it in your Ably [dashboard](https://ably.com/dashboard) under Integrations. - -## Complete example: Live translation - -The following example demonstrates a complete bidirectional integration where messages are processed, translated, and responses are sent back to the chat. - -### Step 1: Client sends message with metadata - - -```javascript -// Client application -await room.messages.send({ - text: "Hello!", - metadata: { - intent: 'translate', - targetLanguage: 'fr' // User specified target language - } -}); -``` - - -### Step 2: Integration rule forwards to webhook - -Configure an integration rule in your dashboard: -- Channel filter: `^chat:.*` (matches all chat rooms) -- Event type: `channel.message` -- Endpoint: `https://your-domain.com/webhook` - -### Step 3: Webhook processes and responds - - -```javascript -const express = require('express'); -const Ably = require('ably'); - -const app = express(); -app.use(express.json()); - -app.post('/webhook', async (req, res) => { - const envelope = req.body; - - // Decode messages - const messages = Ably.Realtime.Message.fromEncodedArray(envelope.messages); - - for (const msg of messages) { - const text = msg.data?.text; - const metadata = msg.data?.metadata || {}; - const roomName = envelope.channel.replace('::$chat', ''); - - // Process based on metadata intent - if (metadata.intent === 'translate') { - const translation = await translateText(text, metadata.targetLanguage); - await sendResponseToChat(roomName, `Translation: ${translation}`); - } - } - - res.status(200).send('OK'); -}); - -async function translateText(text, targetLang) { - // Call your translation service - return `[Translated to ${targetLang}]: ${text}`; -} - -async function sendResponseToChat(roomName, responseText) { - const { ChatClient } = require('@ably/chat'); - const chatClient = new ChatClient({ key: process.env.ABLY_API_KEY }); - - const room = await chatClient.rooms.get(roomName); - - await room.messages.send({ - text: responseText - }, { - extras: { - privileged: { - skipRule: '*' // Prevent infinite loop - } - } - }); -} - -app.listen(3000); -``` - - -### Step 4: Clients receive the response - -All clients subscribed to the room receive the AI assistant's response in real-time: - - -```javascript -room.messages.subscribe((event) => { - console.log('Received:', event.message.text); - // Output: "Translation: [Translated to fr]: Bonjour!" -}); -``` - - -## Key considerations - -Consider the following when integrating external systems with Ably Chat: - -* **Avoiding infinite loops:** Always use the [skip integrations flag](#responding) when responding back to chat to prevent your response from triggering the integration again -* **Scale and reliability:** Different integration methods offer different reliability guarantees. See [choosing an integration method](#implementation-options) for guidance -* **External system limitations:** Your external service may have rate limits, processing constraints, or availability issues. Implement retry logic, circuit breakers, and caching to handle these limitations gracefully -* **Cost optimization:** Only process messages that require processing. Use channel filters and metadata/headers to route messages efficiently -* **Security:** Always validate and sanitize client-provided metadata before using it in your integration logic - -## Next steps - -Explore related documentation to deepen your integration knowledge: - -* [Extract messages via integrations](/docs/chat/rooms/message-extraction) - Complete technical guide for setting up webhooks, queues, and streaming -* [Chat integrations](/docs/chat/integrations) - Technical reference for Chat message structure -* [Platform Integrations](/docs/platform/integrations) - Detailed setup for all integration types -* [Export Chat Data](/docs/guides/chat/export-chat) - Guide for storing chat messages long-term -* [Chat SDK documentation](/docs/chat) - Comprehensive guide to Ably Chat features -* [Chat Moderation](/docs/chat/moderation) - Filter and moderate chat content - -For custom integration requirements or questions, [contact Ably support](https://ably.com/support). From 52f6b8d203985918cf83cb71151c404905355c51 Mon Sep 17 00:00:00 2001 From: Steven Lindsay Date: Tue, 30 Dec 2025 15:47:28 +0000 Subject: [PATCH 29/33] Reduce use of bullet point sections --- .../data-extraction.mdx | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/pages/docs/chat/external-storage-and-processing/data-extraction.mdx b/src/pages/docs/chat/external-storage-and-processing/data-extraction.mdx index dae579a99d..396f719b52 100644 --- a/src/pages/docs/chat/external-storage-and-processing/data-extraction.mdx +++ b/src/pages/docs/chat/external-storage-and-processing/data-extraction.mdx @@ -248,10 +248,8 @@ Configure a webhook integration in your Ably dashboard pointing to your endpoint * [General webhook documentation](/docs/platform/integrations/webhooks) - Overview and other platforms ### Benefits -* Simplest integration method to implement. -* Automatic retry handling with configurable retry windows. -* No additional infrastructure required beyond your webhook endpoint or function. -* Messages can be batched together to reduce invocation overhead. + +Webhooks are the simplest integration method to implement, requiring no additional infrastructure beyond your webhook endpoint or serverless function. They provide automatic retry handling with configurable retry windows, and messages can be batched together to reduce invocation overhead. ### Considerations @@ -295,10 +293,7 @@ See [Ably queues](/docs/platform/integrations/queues) for complete setup details ### Benefits -* Fault-tolerant message delivery -* Messages persist during consumer downtime (up to queue limits) -* Dead letter queue for dropped messages -* At-least-once delivery guarantee +Ably Queues provide fault-tolerant message delivery with at-least-once delivery guarantees. Messages persist during consumer downtime up to queue limits, and a dead letter queue automatically captures dropped messages for monitoring and recovery. ### Considerations @@ -403,9 +398,7 @@ Configure streaming to your target system. See the following for platform-specif ### Benefits -* Leverage existing streaming infrastructure -* Full control over retention and processing -* Massive scale capabilities +Streaming integrations enable you to leverage your existing streaming infrastructure with full control over retention and processing, providing massive scale capabilities for high-volume message flows. ### Considerations From 3870018b4e184386b65ef72bcd1d2e508d5819fa Mon Sep 17 00:00:00 2001 From: Steven Lindsay Date: Tue, 30 Dec 2025 16:57:52 +0000 Subject: [PATCH 30/33] Rename "Message storage and history" to "Message history" --- src/data/nav/chat.ts | 2 +- src/pages/docs/chat/rooms/history.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/data/nav/chat.ts b/src/data/nav/chat.ts index 11bbe4ddd5..b5b513df95 100644 --- a/src/data/nav/chat.ts +++ b/src/data/nav/chat.ts @@ -81,7 +81,7 @@ export default { link: '/docs/chat/rooms/messages', }, { - name: 'Message storage and history', + name: 'Message history', link: '/docs/chat/rooms/history', }, { diff --git a/src/pages/docs/chat/rooms/history.mdx b/src/pages/docs/chat/rooms/history.mdx index cef507e3d9..cb406b995d 100644 --- a/src/pages/docs/chat/rooms/history.mdx +++ b/src/pages/docs/chat/rooms/history.mdx @@ -1,5 +1,5 @@ --- -title: Message storage and history +title: Message history meta_description: "Retrieve previously sent messages from history." --- From c8dfbe460e8b89aeb335335f7f2a652eac8fce8c Mon Sep 17 00:00:00 2001 From: Steven Lindsay Date: Tue, 30 Dec 2025 18:03:02 +0000 Subject: [PATCH 31/33] Reduce bullet point sections and rework language to `data store`. --- .../data-extraction.mdx | 26 +---- .../data-processing.mdx | 32 +----- .../data-storage.mdx | 107 +++++------------- 3 files changed, 40 insertions(+), 125 deletions(-) diff --git a/src/pages/docs/chat/external-storage-and-processing/data-extraction.mdx b/src/pages/docs/chat/external-storage-and-processing/data-extraction.mdx index 396f719b52..2461affa2b 100644 --- a/src/pages/docs/chat/external-storage-and-processing/data-extraction.mdx +++ b/src/pages/docs/chat/external-storage-and-processing/data-extraction.mdx @@ -237,6 +237,8 @@ Read more about [message versioning and sorting](/docs/chat/rooms/messages#order [Outbound webhooks](/docs/platform/integrations/webhooks) enable you to forward messages to HTTP endpoints or serverless functions. +Webhooks are the simplest integration method to implement, requiring no additional infrastructure beyond your webhook endpoint or serverless function. They provide automatic retry handling with configurable retry windows, and messages can be batched together to reduce invocation overhead. + ### Setup Configure a webhook integration in your Ably dashboard pointing to your endpoint. See the following for platform-specific setup guides: @@ -247,9 +249,6 @@ Configure a webhook integration in your Ably dashboard pointing to your endpoint * [Cloudflare Workers](/docs/platform/integrations/webhooks/cloudflare) - Trigger Workers * [General webhook documentation](/docs/platform/integrations/webhooks) - Overview and other platforms -### Benefits - -Webhooks are the simplest integration method to implement, requiring no additional infrastructure beyond your webhook endpoint or serverless function. They provide automatic retry handling with configurable retry windows, and messages can be batched together to reduce invocation overhead. ### Considerations @@ -281,6 +280,8 @@ async function handleWebhook(req, res) { [Ably Queues](/docs/platform/integrations/queues) are traditional message queues that enable you to consume, process, or store chat messages from your servers. +Ably Queues provide fault-tolerant message delivery with at-least-once delivery guarantees. Messages persist during consumer downtime up to queue limits, and a dead letter queue automatically captures dropped messages for monitoring and recovery. + ### Setup Configure an Ably queue integration in your dashboard: @@ -291,10 +292,6 @@ Configure an Ably queue integration in your dashboard: See [Ably queues](/docs/platform/integrations/queues) for complete setup details. -### Benefits - -Ably Queues provide fault-tolerant message delivery with at-least-once delivery guarantees. Messages persist during consumer downtime up to queue limits, and a dead letter queue automatically captures dropped messages for monitoring and recovery. - ### Considerations * **Queue limits**: Default maximum is 10,000 messages. Monitor queue length during peak times to avoid reaching capacity @@ -385,6 +382,8 @@ async function monitorDeadLetterQueue(appID) { [Outbound streaming](/docs/platform/integrations/streaming) enables you to stream a constant flow of chat messages to external streaming or queueing services. +Streaming integrations enable you to leverage your existing streaming infrastructure with full control over retention and processing, providing massive scale capabilities for high-volume message flows. + ### Setup Configure streaming to your target system. See the following for platform-specific setup: @@ -396,9 +395,6 @@ Configure streaming to your target system. See the following for platform-specif * [Apache Pulsar](/docs/platform/integrations/streaming/pulsar) - Stream to Pulsar topics * [General streaming documentation](/docs/platform/integrations/streaming) - Overview and configuration -### Benefits - -Streaming integrations enable you to leverage your existing streaming infrastructure with full control over retention and processing, providing massive scale capabilities for high-volume message flows. ### Considerations @@ -554,13 +550,3 @@ function processMessage(message) { Read more about [message reactions](/docs/chat/rooms/message-reactions) and the [reactions annotation mapping](/docs/chat/integrations#how-to-handle-message-reactions). - - -## Related documentation - -* [Chat integrations](/docs/chat/integrations) - Technical reference for message structure mapping -* [Platform integrations](/docs/platform/integrations) - Complete integration setup guides -* [Message structure](/docs/chat/rooms/messages#structure) - Chat message format details -* [Chat history](/docs/chat/rooms/history) - Retrieve historical messages via API -* [Store messages in your database](/docs/chat/external-storage-and-processing/data-storage) - Guide for long-term storage -* [Process and respond to messages](/docs/chat/external-storage-and-processing/data-processing) - Guide for bidirectional integrations diff --git a/src/pages/docs/chat/external-storage-and-processing/data-processing.mdx b/src/pages/docs/chat/external-storage-and-processing/data-processing.mdx index 29f8f44dde..4bc75091e7 100644 --- a/src/pages/docs/chat/external-storage-and-processing/data-processing.mdx +++ b/src/pages/docs/chat/external-storage-and-processing/data-processing.mdx @@ -36,34 +36,19 @@ Ably provides several different integration methods, each suited to different us ### Webhooks -[Outbound webhooks](/docs/platform/integrations/webhooks) are ideal for simple business logic and event-driven actions. - -**Best for:** -* Processing @mentions or triggering custom notifications -* Executing lightweight business logic (lookup, validation, etc.) -* Serverless architectures (AWS Lambda, Cloud Functions, etc.) +[Outbound webhooks](/docs/platform/integrations/webhooks) are ideal for simple business logic and event-driven actions, particularly for processing @mentions or triggering custom notifications, executing lightweight business logic (lookup, validation, etc.), and serverless architectures (AWS Lambda, Cloud Functions, etc.). **Learn more:** See [Extract messages via integrations](/docs/chat/external-storage-and-processing/data-extraction#webhooks) for setup details and code examples. ### Ably Queues -[Ably Queues](/docs/platform/integrations/queues) provide strict ordering and fault-tolerant delivery. - -**Best for:** -* Automated support flows where message order matters -* Processing sequential events (game moves, transaction steps) -* Fault-tolerant message processing with automatic retries +[Ably Queues](/docs/platform/integrations/queues) provide strict ordering and fault-tolerant delivery, making them ideal for automated support flows where message order matters, processing sequential events (game moves, transaction steps), and fault-tolerant message processing with automatic retries. **Learn more:** See [Extract messages via integrations](/docs/chat/external-storage-and-processing/data-extraction#queues) for setup details and code examples. ### Streaming -[Outbound streaming](/docs/platform/integrations/streaming) enables massive scale and integration with existing infrastructure. - -**Best for:** -* High-throughput scenarios (large-scale chats, millions of messages) -* Existing streaming infrastructure (Kafka, Kinesis, etc.) -* Complex data pipelines and analytics +[Outbound streaming](/docs/platform/integrations/streaming) enables massive scale and integration with existing infrastructure, making it ideal for high-throughput scenarios (large-scale chats, millions of messages), existing streaming infrastructure (Kafka, Kinesis, etc.), and complex data pipelines and analytics. **Learn more:** See [Extract messages via integrations](/docs/chat/external-storage-and-processing/data-extraction#streaming) for setup details and code examples. @@ -284,14 +269,3 @@ Consider the following when integrating external systems with Ably Chat: * **Cost optimization:** Only process messages that require processing. Use channel filters and metadata/headers to route messages efficiently * **Security:** Always validate and sanitize client-provided metadata before using it in your integration logic * **Error handling:** Handle external system failures gracefully - consider fallback responses or queuing failed requests for retry - -## Related documentation - -* [Extract messages via integrations](/docs/chat/external-storage-and-processing/data-extraction) - Complete technical guide for setting up webhooks, queues, and streaming -* [Store messages in your database](/docs/chat/external-storage-and-processing/data-storage) - Guide for long-term storage -* [Chat integrations](/docs/chat/integrations) - Technical reference for Chat message structure -* [Platform Integrations](/docs/platform/integrations) - Detailed setup for all integration types -* [Chat SDK documentation](/docs/chat) - Comprehensive guide to Ably Chat features -* [Chat Moderation](/docs/chat/moderation) - Filter and moderate chat content - -For custom integration requirements or questions, [contact Ably support](https://ably.com/support). diff --git a/src/pages/docs/chat/external-storage-and-processing/data-storage.mdx b/src/pages/docs/chat/external-storage-and-processing/data-storage.mdx index 518cb39d52..61426ae2da 100644 --- a/src/pages/docs/chat/external-storage-and-processing/data-storage.mdx +++ b/src/pages/docs/chat/external-storage-and-processing/data-storage.mdx @@ -1,33 +1,27 @@ --- -title: "Store messages in your database" -meta_description: "Store chat messages from Ably Chat in your own database for long-term retention, compliance, and analytics." -meta_keywords: "chat storage, database, data retention, compliance, analytics, message history, chat export" +title: "Store messages" +meta_description: "Store chat messages from Ably Chat in your own data storage for long-term retention, compliance, and analytics." +meta_keywords: "chat storage, data storage, data retention, compliance, analytics, message history, chat export" --- -Store chat messages from Ably Chat in your own database for long-term retention, compliance, analytics, or to maintain your database as the canonical source of truth. +Store chat messages from Ably Chat in your own data storage for long-term retention, compliance, analytics, or to maintain your data storage as the canonical source of truth. While Ably Chat provides flexible data retention for messages (30 days by default, up to a year on request), this page discusses options for longer-term storage and additional control over your chat data. ## Why store chat data? -Storing chat data in your own database enables many use cases: - -- **Compliance and legal requirements**: Meet data retention policies, maintain audit trails for support conversations, or fulfill regulatory requirements -- **Analytics and business intelligence**: Build dashboards, train ML models, analyze customer sentiment, or track support quality metrics -- **Enhanced functionality**: Implement features that need chat history, such as full-text search across all messages -- **Single source of truth**: Maintain your database as the canonical source of data -- **Long-term storage**: Store chat data for longer than Ably Chat's retention period +Storing chat data in your own data storage enables many use cases. You can meet compliance and legal requirements by maintaining data retention policies, audit trails for support conversations, and fulfilling regulatory requirements. It enables analytics and business intelligence through building dashboards, training ML models, analyzing customer sentiment, and tracking support quality metrics. You can implement enhanced functionality that needs chat history, such as full-text search across all messages, maintain your data storage as the canonical source of truth, and store chat data for longer than Ably Chat's retention period. ## Key considerations Consider the following when storing chat data: -### Database schema design +### Data storage schema design Design your schema to support the features you need while considering scale and reliability: -- **Database schema**: Design your schema to allow you to easily build the features you need, keeping in mind scale and reliability. +- **Data storage schema**: Design your schema to allow you to easily build the features you need, keeping in mind scale and reliability. - **Version history requirements**: Decide whether you need to store all versions of messages or just the latest version (see [Decision 1](#decision-1) below) -- **Concurrent writes**: New messages, updates, and deletes will arrive concurrently, so your database must handle this. Consider reducing roundtrips, managing locks, and handling race conditions +- **Concurrent writes**: New messages, updates, and deletes will arrive concurrently, so your data storage must handle this. Consider reducing roundtrips, managing locks, and handling race conditions - **Indexing strategy**: Plan indexes for efficient queries on room name, timestamp, serial, and version serial - **Storage optimization**: Consider whether to store full message objects or normalized relational data @@ -35,27 +29,27 @@ Design your schema to support the features you need while considering scale and Depending on your application's scale, consider: -- **Message ingestion throughput**: How many messages per second will your database need to handle? +- **Message ingestion throughput**: How many messages per second will your data storage need to handle? - **Consumer scaling**: How will you scale up consumers during peak times? -- **Database performance**: Will your database handle the write volume? Consider write-optimized databases or batch writes +- **Data storage performance**: Will your data storage handle the write volume? Consider write-optimized databases or batch writes - **Query performance**: How will you efficiently retrieve messages for chat windows or search results? ### Data latency and consistency -When using integrations, there will be a small delay between a message being published and it arriving in your database: +When using integrations, there will be a small delay between a message being published and it arriving in your data storage: - **Integration delay**: Typically milliseconds to seconds depending on the integration method -- **Database write time**: Additional time to write to your database -- **Eventual consistency**: Accept that your database will be eventually consistent with Ably Chat +- **Data storage write time**: Additional time to write to your data storage +- **Eventual consistency**: Accept that your data storage will be eventually consistent with Ably Chat -If you need your database to be immediately consistent, consider [publishing via your own servers](#publish-via-own). +If you need your data storage to be immediately consistent, consider [publishing via your own servers](#publish-via-own). ### Messages beyond retention period Consider how to retrieve and display messages older than Ably Chat's retention period: -- **Hydrating chat windows**: Can you efficiently fetch recent messages from both your database and Ably Chat history? -- **Mixed sources**: How will you merge messages from your database with messages from Ably Chat history? +- **Hydrating chat windows**: Can you efficiently fetch recent messages from both your data storage and Ably Chat history? +- **Mixed sources**: How will you merge messages from your data storage with messages from Ably Chat history? - **UI consistency**: How will you indicate to users which messages came from long-term storage? ## Implementation approaches @@ -64,18 +58,11 @@ Choose the approach that fits your requirements: ### 1. Using integrations (recommended) -Extract messages via [webhooks, queues, or streaming](/docs/chat/external-storage-and-processing/data-extraction) and save them to your database. This is the recommended approach for most use cases. +Extract messages via [webhooks, queues, or streaming](/docs/chat/external-storage-and-processing/data-extraction) and save them to your data storage. This is the recommended approach for most use cases. -**Benefits:** -- Proven, scalable solution -- Leverage Ably's infrastructure -- Multiple integration methods to choose from -- Monitoring and error handling built-in +This approach provides a proven, scalable solution that leverages Ably's infrastructure with multiple integration methods to choose from and built-in monitoring and error handling. -**When to use:** -- You want reliable, continuous ingestion -- You're comfortable with eventual consistency (milliseconds to seconds delay) -- You need to store messages from ongoing or long-running chats +Use this approach when you want reliable, continuous ingestion, you're comfortable with eventual consistency (milliseconds to seconds delay), and you need to store messages from ongoing or long-running chats. See the [data extraction guide](/docs/chat/external-storage-and-processing/data-extraction) for complete setup details. @@ -83,11 +70,7 @@ See the [data extraction guide](/docs/chat/external-storage-and-processing/data- Proxy message publishing through your own servers to save messages as they're produced. -**Benefits:** -- Full control over publishing flow -- Immediate consistency (no integration delay) -- Opportunity to add validation or business logic before publishing -- Can use Chat REST API or SDK to publish +This approach provides full control over the publishing flow with immediate consistency (no integration delay), the opportunity to add validation or business logic before publishing, and the ability to use the Chat REST API or SDK to publish. **Considerations:** - You must handle updates and deletes yourself, including all consistency issues @@ -96,19 +79,13 @@ Proxy message publishing through your own servers to save messages as they're pr - Must handle the scale of realtime publishes - Keeping both systems in sync is complex - need mitigation strategies for all failure scenarios -**When to use:** -- Your database must be immediately consistent -- You need to validate or transform messages before publishing -- You already proxy messages through your servers for other reasons +Use this approach when your data storage must be immediately consistent, you need to validate or transform messages before publishing, or you already proxy messages through your servers for other reasons. ### 3. Using the Chat History endpoint Fetch completed chat histories using the [Chat History endpoint](/docs/api/chat-rest#tag/rooms/paths/~1chat~1%7Bversion%7D~1rooms~1%7BroomName%7D~1messages/get) or [Chat SDK](/docs/chat/rooms/history). -**Benefits:** -- Simple, reliable solution for archival -- No integration setup required -- Perfect for chats with clear start and end times +This approach provides a simple, reliable solution for archival with no integration setup required, and is perfect for chats with clear start and end times. **Considerations:** - Intended for pre-filling chat windows, not continuous ingestion @@ -117,16 +94,13 @@ Fetch completed chat histories using the [Chat History endpoint](/docs/api/chat- - Must decide when to trigger export (closed ticket, ended session, etc.) - Impractical for long-running chats if you need all updates/deletes (must fetch from start each time) -**When to use:** -- Archiving completed chats (closed support tickets, ended game sessions) -- Clear start and end boundaries for chats -- Don't need continuous ingestion or full version history +Use this approach when archiving completed chats (closed support tickets, ended game sessions), there are clear start and end boundaries for chats, and you don't need continuous ingestion or full version history. Use the [`[meta]channel.lifecycle`](/docs/metadata-stats/metadata/subscribe#channel-lifecycle) metachannel to detect when channels close, or use your business logic (ticket closed, session ended) to trigger exports. ## Storing messages -After [extracting messages via integrations](/docs/chat/external-storage-and-processing/data-extraction), you need to make decisions about how to store them in your database. +After [extracting messages via integrations](/docs/chat/external-storage-and-processing/data-extraction), you need to make decisions about how to store them in your data storage. ### Decision 1: Full version history or just the latest version? @@ -136,7 +110,7 @@ Do you need all versions of a message or just the latest version? - If you need to store all versions of a message, uniquely index by `roomName`, `serial` and `version.serial`. - If you only need the latest version of a message, uniquely index by `roomName` and `serial`, and only update if the received `version.serial` is greater than what you have stored. This handles out-of-order delivery. - When performing a message search or lookup, do you want to return only the latest version of each message, even if you store the full version history? - - If you are looking to hydrate chat windows from your own database, think of how to efficiently retrieve the latest version of each message for a time window. For example, this can be implemented via a separate table or by iterating through all versions and filtering old versions out. + - If you are looking to hydrate chat windows from your own data storage, think of how to efficiently retrieve the latest version of each message for a time window. For example, this can be implemented via a separate table or by iterating through all versions and filtering old versions out. ```javascript @@ -177,16 +151,14 @@ Read the guide on [outbound webhooks](/docs/platform/integrations/webhooks) for You need to consider: - **Redundancy**: In case of failure, Ably will retry delivering the message to your webhook, but only for a short period. You can see errors in the [`[meta]log` channel](/docs/platform/errors#meta). - **Ordering**: Messages can arrive out-of-order. You can sort them using their `serial` and `version.serial` properties. -- **Consistency**: Webhook calls that fail will lead to inconsistencies between your database and Ably, which can be difficult to resolve. Detect if this happens using the `[meta]log` channel and use the [history endpoint](#history-endpoint) to backfill missing data. +- **Consistency**: Webhook calls that fail will lead to inconsistencies between your data storage and Ably, which can be difficult to resolve. Detect if this happens using the `[meta]log` channel and use the [history endpoint](#history-endpoint) to backfill missing data. - **[At-least-once delivery](/docs/platform/architecture/idempotency#protocol-support-for-exactly-once-delivery)**: You need to handle duplicate messages. Deduplication can be done by checking `serial` and `version.serial`. ## Using outbound streaming Ably can stream messages directly to your own queueing or streaming service: Kinesis, Kafka, AMQP, SQS. Read the guide on [outbound streaming](/docs/platform/integrations/streaming) for more details on how to set up the streaming integration with Ably for the service of your choice. -Benefits: -- Use your existing queue system to process and save messages from Ably. -- You control your own queue system, so you have full control over message ingestion from queue to database in terms of retry strategies, retention policies, queue lengths, and so on. +This approach enables you to use your existing queue system to process and save messages from Ably, giving you full control over message ingestion from queue to data storage in terms of retry strategies, retention policies, queue lengths, and so on. You need to consider: - You need to maintain and be responsible for a reliable queue system. If you don't already have such a system, it increases complexity on your end. @@ -194,14 +166,11 @@ You need to consider: ## Using an Ably queue -Ably can forward messages from chat room channels to an [Ably Queue](/docs/platform/integrations/queues), which you can then consume from your own servers to save messages to your own database. Read the guide on [Ably queues](/docs/platform/integrations/queues) for more details on how to set up the queue integration with Ably. +Ably can forward messages from chat room channels to an [Ably Queue](/docs/platform/integrations/queues), which you can then consume from your own servers to save messages to your own data storage. Read the guide on [Ably queues](/docs/platform/integrations/queues) for more details on how to set up the queue integration with Ably. Ably ensures that each message is delivered to only one consumer even if multiple consumers are connected. -Benefits of using an Ably queue: -- You can consume it from your servers, meaning overall this is fault-tolerant. Ably takes care of the complexity of maintaining a queue. -- You can use multiple queues and configure which channels go to which queue via regex filters on the channel name. -- Fault-tolerant: if your systems suffer any temporary downtime, you will not miss messages, up to the queue max size. There is a deadletter queue to handle the situation where messages are dropped from the Ably Queue. +Using an Ably queue provides a fault-tolerant solution where you can consume messages from your servers while Ably takes care of the complexity of maintaining a queue. You can use multiple queues and configure which channels go to which queue via regex filters on the channel name. If your systems suffer any temporary downtime, you will not miss messages up to the queue max size, and a deadletter queue handles situations where messages are dropped from the Ably Queue. You need to consider: - During peak times you may need to scale up your consumers to avoid overloading the queue past the maximum queue length allowed. @@ -213,19 +182,14 @@ You need to consider: Change the publish path: instead of publishing Chat messages, updates, and deletes to Ably directly, proxy them through your own server. This gives you the opportunity to also save the messages as they are produced, and also apply different validation schemes if needed. -Benefits: -- Full control over publishing. -- Opportunity to add extra validation before publishing to Ably. -- Opportunity to add extra processing or business logic before publishing to Ably. -- You can publish messages directly via the Chat REST API, and avoid having to encode/decode Chat Messages to and from Ably Pub/Sub messages. -- If you are using a supported language, you have the option to publish via the Chat SDK. +This approach provides full control over publishing with the opportunity to add extra validation or processing and business logic before publishing to Ably. You can publish messages directly via the Chat REST API and avoid having to encode/decode Chat Messages to and from Ably Pub/Sub messages, and if you are using a supported language, you have the option to publish via the Chat SDK. You need to consider: - You need to handle updates and deletes on your own, including all consistency issues that arise from this. - Storing message reactions will require using one of the other methods presented in this guide, otherwise you will not have access to the aggregates (summaries) that Ably provides. - Your own servers are in the middle of the message publish path, so they can become a bottleneck in availability and will add latency in the publish path. - Your own servers will need to handle the scale you operate at for realtime publishes. -- Keeping both systems in sync can be a difficult problem to solve in all edge cases. Inconsistencies can happen if either publishing to Ably or saving to your own database fails. You will need mitigation strategies to handle all failure scenarios to ensure eventual consistency. +- Keeping both systems in sync can be a difficult problem to solve in all edge cases. Inconsistencies can happen if either publishing to Ably or saving to your own data storage fails. You will need mitigation strategies to handle all failure scenarios to ensure eventual consistency. ## Using the Chat History endpoint @@ -240,12 +204,3 @@ The intended use of the chat history endpoint is to retrieve messages for pre-fi - You can import the same room multiple times (deduplicate by `serial` and `version.serial`). However, to capture all updates and deletes, you will need to fetch from the first message each time, which can be impractical for long-running chats with large message histories. For use cases where there is a clear start and end of the chat, exporting the chat via history requests is a simple, reliable solution. If there is no clear start and end for chats, if you require continuous ingestion, or if you need the full message version history, please consider using one of the other methods mentioned in this guide. - - -## Related documentation - -* [Extract messages via integrations](/docs/chat/external-storage-and-processing/data-extraction) - Complete setup guide for webhooks, queues, and streaming -* [Chat integrations](/docs/chat/integrations) - Technical reference for message structure mapping -* [Message structure](/docs/chat/rooms/messages#structure) - Chat message format details -* [Chat history](/docs/chat/rooms/history) - Retrieve historical messages via API -* [Process and respond to messages](/docs/chat/external-storage-and-processing/data-processing) - Guide for bidirectional integrations From faab8e40a91d42eaaf4806312afb4d820d302502 Mon Sep 17 00:00:00 2001 From: Steven Lindsay Date: Tue, 30 Dec 2025 18:31:18 +0000 Subject: [PATCH 32/33] Update and rework "Data storage" page to more the chat history section and rename the page. --- .../data-storage.mdx | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/pages/docs/chat/external-storage-and-processing/data-storage.mdx b/src/pages/docs/chat/external-storage-and-processing/data-storage.mdx index 61426ae2da..1ebad5540f 100644 --- a/src/pages/docs/chat/external-storage-and-processing/data-storage.mdx +++ b/src/pages/docs/chat/external-storage-and-processing/data-storage.mdx @@ -1,5 +1,5 @@ --- -title: "Store messages" +title: "Data storage" meta_description: "Store chat messages from Ably Chat in your own data storage for long-term retention, compliance, and analytics." meta_keywords: "chat storage, data storage, data retention, compliance, analytics, message history, chat export" --- @@ -8,7 +8,17 @@ Store chat messages from Ably Chat in your own data storage for long-term retent While Ably Chat provides flexible data retention for messages (30 days by default, up to a year on request), this page discusses options for longer-term storage and additional control over your chat data. -## Why store chat data? +## What Ably Chat history gives you + +You can fetch the message history of a chat room using the [Chat History endpoint](/docs/api/chat-rest#tag/rooms/paths/~1chat~1%7Bversion%7D~1rooms~1%7BroomName%7D~1messages/get) or the [Chat SDK](/docs/chat/rooms/history). The chat room history endpoint is a paginated HTTP endpoint that allows you to retrieve messages from a chat room. The Chat SDK provides a convenient way to fetch the history of a chat room. + +The intended use of the chat history endpoint is to retrieve messages for pre-filling a chat window, not for continuous ingestion into other systems. As a result, there are some important things to consider: +- The history endpoint is not a changelog, it is a snapshot of the messages in the room at the time the request is made. It returns only the latest version of each message. +- The history API returns messages in their canonical global order (sorted by `serial`). +- During some offline period, to capture all updates and deletes that may have occurred, you will need to re-fetch all messages each time, which can be impractical for long-running chats with large message histories. +- The maximum retention period for chat messages is 365 days (on request). Messages older than this will not be available via the history endpoint. + +## Why store chat data externally? Storing chat data in your own data storage enables many use cases. You can meet compliance and legal requirements by maintaining data retention policies, audit trails for support conversations, and fulfilling regulatory requirements. It enables analytics and business intelligence through building dashboards, training ML models, analyzing customer sentiment, and tracking support quality metrics. You can implement enhanced functionality that needs chat history, such as full-text search across all messages, maintain your data storage as the canonical source of truth, and store chat data for longer than Ably Chat's retention period. @@ -93,6 +103,8 @@ This approach provides a simple, reliable solution for archival with no integrat - Returns messages sorted by `serial` (canonical global order) - Must decide when to trigger export (closed ticket, ended session, etc.) - Impractical for long-running chats if you need all updates/deletes (must fetch from start each time) +- You will need to decide when and which rooms to import messages from. The metachannel [`[meta]channel.lifecycle`](/docs/metadata-stats/metadata/subscribe#channel-lifecycle) provides events when channels are opened and closed, but your business logic might provide a better solution. For example, import when a support ticket is closed or game session ends. +- You can import the same room multiple times (deduplicate by `serial` and `version.serial`). However, to capture all updates and deletes, you will need to fetch from the first message each time, which can be impractical for long-running chats with large message histories. Use this approach when archiving completed chats (closed support tickets, ended game sessions), there are clear start and end boundaries for chats, and you don't need continuous ingestion or full version history. @@ -190,17 +202,3 @@ You need to consider: - Your own servers are in the middle of the message publish path, so they can become a bottleneck in availability and will add latency in the publish path. - Your own servers will need to handle the scale you operate at for realtime publishes. - Keeping both systems in sync can be a difficult problem to solve in all edge cases. Inconsistencies can happen if either publishing to Ably or saving to your own data storage fails. You will need mitigation strategies to handle all failure scenarios to ensure eventual consistency. - -## Using the Chat History endpoint - -You can fetch the message history of a chat room using the [Chat History endpoint](/docs/api/chat-rest#tag/rooms/paths/~1chat~1%7Bversion%7D~1rooms~1%7BroomName%7D~1messages/get) or the [Chat SDK](/docs/chat/rooms/history). The chat room history endpoint is a paginated HTTP endpoint that allows you to retrieve messages from a chat room. The Chat SDK provides a convenient way to fetch the history of a chat room. - -If your use case is to archive chats that have ended, such as to export the chat history of a support ticket that is closed, you can use the chat history endpoint to export the messages to your own system. Read the docs on [chat history](/docs/chat/rooms/history) for more details. - -The intended use of the chat history endpoint is to retrieve messages for pre-filling a chat window, not for continuous ingestion into other systems. As a result, there are some important things to consider: -- The history endpoint is not a changelog, it is a snapshot of the messages in the room at the time the request is made. It returns only the latest version of each message. -- The history API returns messages in their canonical global order (sorted by `serial`). -- You will need to decide when and which rooms to import messages from. The metachannel [`[meta]channel.lifecycle`](/docs/metadata-stats/metadata/subscribe#channel-lifecycle) provides events when channels are opened and closed, but your business logic might provide a better solution, for example import when a support ticket is closed or game session ends. -- You can import the same room multiple times (deduplicate by `serial` and `version.serial`). However, to capture all updates and deletes, you will need to fetch from the first message each time, which can be impractical for long-running chats with large message histories. - -For use cases where there is a clear start and end of the chat, exporting the chat via history requests is a simple, reliable solution. If there is no clear start and end for chats, if you require continuous ingestion, or if you need the full message version history, please consider using one of the other methods mentioned in this guide. From 6b0ffcc2c56a2dbcc9e1093bff0597c80f76bf7f Mon Sep 17 00:00:00 2001 From: Steven Lindsay Date: Mon, 5 Jan 2026 18:38:04 +0000 Subject: [PATCH 33/33] Remove message extraction guide and navigation link from main feature section --- src/data/nav/chat.ts | 4 - .../docs/chat/rooms/message-extraction.mdx | 513 ------------------ 2 files changed, 517 deletions(-) delete mode 100644 src/pages/docs/chat/rooms/message-extraction.mdx diff --git a/src/data/nav/chat.ts b/src/data/nav/chat.ts index b5b513df95..de422b6273 100644 --- a/src/data/nav/chat.ts +++ b/src/data/nav/chat.ts @@ -112,10 +112,6 @@ export default { name: 'Message replies', link: '/docs/chat/rooms/replies', }, - { - name: 'Message extraction', - link: '/docs/chat/rooms/message-extraction', - }, ], }, { diff --git a/src/pages/docs/chat/rooms/message-extraction.mdx b/src/pages/docs/chat/rooms/message-extraction.mdx deleted file mode 100644 index b56fd02b26..0000000000 --- a/src/pages/docs/chat/rooms/message-extraction.mdx +++ /dev/null @@ -1,513 +0,0 @@ ---- -title: "Extract messages via integrations" -meta_description: "Extract chat messages from Ably Chat using integrations for external processing, storage, or analysis." -meta_keywords: "chat integrations, message extraction, webhooks, streaming, queues, message processing, chat data" ---- - -Extract chat messages from Ably Chat rooms to external systems using Ably's integration capabilities. This enables you to process, store, or analyze messages outside of Ably Chat while maintaining realtime message delivery to chat participants. - -Chat rooms are built on Ably Pub/Sub channels, allowing you to leverage the full range of Ably's [platform integrations](/docs/platform/integrations) to forward messages to external systems. - -## Integration methods - -Ably provides three primary methods for extracting chat messages: - -1. **[Webhooks](#webhooks)** - Forward messages to [HTTP endpoints](/docs/platform/integrations/webhooks/generic), [AWS Lambda](/docs/platform/integrations/webhooks/lambda), [Google Cloud Functions](/docs/platform/integrations/webhooks/gcp-function), [Cloudflare Workers](/docs/platform/integrations/webhooks/cloudflare), and more -2. **[Queues](#queues)** - Route messages to [Ably-managed queues](/docs/platform/integrations/queues) for consumption by your services -3. **[Streaming](#streaming)** - Stream messages to external systems like [Kafka](/docs/platform/integrations/streaming/kafka), [Kinesis](/docs/platform/integrations/streaming/kinesis), [SQS](/docs/platform/integrations/streaming/sqs), [AMQP](/docs/platform/integrations/streaming/amqp), or [Pulsar](/docs/platform/integrations/streaming/pulsar) - -Each method offers different trade-offs in terms of simplicity, reliability, and infrastructure requirements. - -## Filtering rooms - -Control which chat rooms trigger integrations using channel name filters. - -### Setting up filters - -When configuring an integration rule in your Ably dashboard: - -* **Channel filter**: Use a regular expression to match room names. For example, `^support:.*` matches all rooms starting with `support:` -* **Event type**: Use `channel.message` to forward chat messages and exclude presence events -* **Enveloped messages**: Enable to receive full message metadata including `serial`, `version`, and `headers` - -**Note**: Chat rooms use the `::$chat` suffix on channel names internally. Integration filters match against the full channel name, but you don't need to include the suffix in your filter pattern. - -### Example filter configuration - - -```javascript -// Room name: support:ticket-123 -// Internal channel: support:ticket-123::$chat -// Filter pattern: ^support:.* -// Result: Messages from this room will be forwarded - -const supportRoom = await chatClient.rooms.get('support:ticket-123'); -await supportRoom.messages.send({ text: 'Help needed' }); // Will trigger integration -``` - - -## Decoding messages - -Messages received through integrations are encoded as Ably Pub/Sub messages and need to be decoded into Chat messages. Full details on the mapping are available in the [Chat integrations](/docs/chat/integrations) documentation. - -### Understanding enveloped messages - -With enveloping enabled (recommended), messages arrive wrapped in metadata: - - -```javascript -{ - "source": "channel.message", - "appId": "your-app-id", - "channel": "support:ticket-123::$chat", - "ruleId": "integration-rule-id", - "messages": [ - { - "id": "unique-message-id", - "clientId": "user-123", - "name": "chat.message", - "timestamp": 1234567890, - "serial": "01765820788939-000@108wgxjJwBwuAB37648671:000", - "action": 0, - "data": { - "text": "Message content", - "metadata": {} - }, - "extras": { - "headers": {} - } - } - ] -} -``` - - -### Extracting room name - -Remove the `::$chat` suffix to get the room name: - - -```javascript -function extractRoomName(channelName) { - // channelName: "support:ticket-123::$chat" - return channelName.replace('::$chat', ''); // Returns: "support:ticket-123" -} -``` - - -### Decoding message data - -Use the Ably SDK to decode messages from the envelope: - - -```javascript -const Ably = require('ably'); - -function decodeMessages(envelopeData) { - const decodedMessages = Ably.Realtime.Message.fromEncodedArray(envelopeData.messages); - - return decodedMessages.map(msg => ({ - serial: msg.serial, - text: msg.data?.text, - metadata: msg.data?.metadata || {}, - headers: msg.extras?.headers || {}, - clientId: msg.clientId, - timestamp: msg.timestamp, - action: msg.action, - versionSerial: msg.version?.serial || msg.serial - })); -} -``` - - -## Message versioning - -Chat messages have a versioning system for updates and deletes: - -* **`serial`**: Unique identifier for the original message -* **`version.serial`**: Identifier for the specific message version -* **`action`**: Indicates message type (create, update, delete, or summary for reactions) - -### Handling message versions - - -```javascript -function processMessage(message) { - // Discard reaction summaries if not needed - if (message.action === 'message.summary') { - return; - } - - // Process based on action - switch (message.action) { - case 'message.created': - handleNewMessage(message); - break; - case 'message.updated': - handleMessageUpdate(message); - break; - case 'message.deleted': - handleMessageDelete(message); - break; - } -} -``` - - -### Version ordering - -Version serials are lexicographically ordered. A higher `version.serial` indicates a newer version: - - -```javascript -function isNewerVersion(existingVersion, newVersion) { - return newVersion > existingVersion; -} - -// Only process if this is a newer version -if (isNewerVersion(stored.version.serial, incoming.version.serial)) { - updateMessage(incoming); -} -``` - - -Read more about [message versioning and sorting](/docs/chat/rooms/messages#ordering-update-delete) in the messages documentation. - -## Using webhooks - -[Outbound webhooks](/docs/platform/integrations/webhooks) enable you to forward messages to HTTP endpoints or serverless functions. - -### Setup - -Configure a webhook integration in your Ably dashboard pointing to your endpoint. See the following for platform-specific setup guides: - -* [Generic HTTP webhook](/docs/platform/integrations/webhooks/generic) - Forward to any HTTP/HTTPS endpoint -* [AWS Lambda](/docs/platform/integrations/webhooks/lambda) - Trigger Lambda functions -* [Google Cloud Functions](/docs/platform/integrations/webhooks/gcp-function) - Invoke GCP functions -* [Cloudflare Workers](/docs/platform/integrations/webhooks/cloudflare) - Trigger Workers -* [General webhook documentation](/docs/platform/integrations/webhooks) - Overview and other platforms - -### Benefits -* Simplest integration method to implement. -* Automatic retry handling with configurable retry windows. -* No additional infrastructure required beyond your webhook endpoint or function. -* Messages can be batched together to reduce invocation overhead. - -### Considerations - -* **Retry behavior**: For Lambda, AWS automatically retries failed invocations up to two times with delays (1 minute, then 2 minutes). For generic HTTP webhooks, Ably retries for a limited window. -* **Ordering**: Messages may arrive out-of-order; use `serial` and `version.serial` to sort. -* **Message Loss**: Messages may be dropped if retries exceed the retry window. -* **Deduplication**: Handle potential duplicates using message serials - webhooks provide at-least-once delivery. -* **Monitoring**: Use [`[meta]log` channel](/docs/metadata-stats/metadata/subscribe#log) to detect failures (see [monitoring section](#monitoring-performance)). - -### Example webhook handler - - -```javascript -async function handleWebhook(req, res) { - const envelope = req.body; - const roomName = extractRoomName(envelope.channel); - const messages = decodeMessages(envelope); - - for (const message of messages) { - await processMessage(roomName, message); - } - - res.status(200).send('OK'); -} -``` - - -## Using queues - -[Ably Queues](/docs/platform/integrations/queues) are traditional message queues that enable you to consume, process, or store chat messages from your servers. - -### Setup - -Configure an Ably queue integration in your dashboard: - -1. [Provision a queue](/docs/platform/integrations/queues#provision) with your desired region, TTL, and max length settings -2. [Create a queue rule](/docs/platform/integrations/queues#config) to route chat messages to the queue -3. Consume messages using [AMQP](/docs/platform/integrations/queues#amqp) or [STOMP](/docs/platform/integrations/queues#stomp) protocols - -See [Ably queues](/docs/platform/integrations/queues) for complete setup details. - -### Benefits - -* Fault-tolerant message delivery -* Messages persist during consumer downtime (up to queue limits) -* Dead letter queue for dropped messages -* At-least-once delivery guarantee - -### Considerations - -* **Queue limits**: Default maximum is 10,000 messages. Monitor queue length during peak times to avoid reaching capacity -* **TTL**: Messages expire after 60 minutes (default/max) if not consumed -* **Dead letter queue**: Automatically provisioned. Always consume to monitor for dropped or expired messages -* **Throughput**: Multi-tenanted queues support up to 200 messages/second per account. For higher volumes, consider [dedicated queues or streaming](/docs/platform/integrations/queues#scalability) -* **Ordering**: Messages maintain order per channel with a single consumer. Multiple consumers or multi-channel messages may affect ordering - -### Example queue consumer (AMQP) - - -```javascript -const amqp = require('amqplib'); - -// Queue name format: APPID:queue-name -const queueName = 'your-app-id:chat-messages'; -// Avoid hardcoding credentials in production -const url = 'amqps://APPID.KEYID:SECRET@us-east-1-a-queue.ably.io/shared'; - -amqp.connect(url, (err, conn) => { - if (err) { return console.error(err); } - - conn.createChannel((err, ch) => { - if (err) { return console.error(err); } - - // Subscribe to the queue - ch.consume(queueName, (item) => { - const envelope = JSON.parse(item.content); - const roomName = extractRoomName(envelope.channel); - - // Decode messages using Ably SDK - const messages = Ably.Realtime.Message.fromEncodedArray(envelope.messages); - - messages.forEach(async (message) => { - await processMessage(roomName, message); - }); - - // Acknowledge message to remove from queue - ch.ack(item); - }); - }); -}); -``` - - -### Monitor the dead letter queue - -Ably automatically provisions a [dead letter queue](/docs/platform/integrations/queues#deadletter) when you create a queue. Messages are moved to the dead letter queue when they: - -* Are rejected by consumers (`basic.reject` or `basic.nack` with `requeue=false`) -* Exceed their TTL and expire -* Cause the queue to reach maximum capacity (oldest messages dropped) - -**Important:** Monitor your dead letter queue to detect processing failures and capacity issues. - - -```javascript -async function monitorDeadLetterQueue(appID) { - // Dead letter queue name format: APPID:deadletter - const dlqName = `${appID}:deadletter`; - // Avoid hardcoding credentials in production - const url = 'amqps://APPID.KEYID:SECRET@us-east-1-a-queue.ably.io/shared'; - - amqp.connect(url, (err, conn) => { - if (err) { return console.error(err); } - - conn.createChannel((err, ch) => { - if (err) { return console.error(err); } - - ch.consume(dlqName, (item) => { - const envelope = JSON.parse(item.content); - console.error('Message dropped from queue:', envelope); - - // Handle failed message (alert, retry, etc.) - handleFailedMessage(envelope); - - ch.ack(item); - }); - }); - }); -} -``` - - -**Note:** Only one dead letter queue exists per Ably application, shared across all queues. - -## Using streaming - -[Outbound streaming](/docs/platform/integrations/streaming) enables you to stream a constant flow of chat messages to external streaming or queueing services. - -### Setup - -Configure streaming to your target system. See the following for platform-specific setup: - -* [Apache Kafka](/docs/platform/integrations/streaming/kafka) - Stream to Kafka topics -* [AWS Kinesis](/docs/platform/integrations/streaming/kinesis) - Stream to Kinesis streams -* [AWS SQS](/docs/platform/integrations/streaming/sqs) - Stream to SQS queues -* [AMQP](/docs/platform/integrations/streaming/amqp) - Stream to AMQP brokers -* [Apache Pulsar](/docs/platform/integrations/streaming/pulsar) - Stream to Pulsar topics -* [General streaming documentation](/docs/platform/integrations/streaming) - Overview and configuration - -### Benefits - -* Leverage existing streaming infrastructure -* Full control over retention and processing -* Massive scale capabilities - -### Considerations - -* **Infrastructure**: You manage and maintain the streaming system -* **Reliability**: Messages lost if system unavailable -* **Complexity**: Higher operational overhead - -### Example Kafka consumer - - -```javascript -const { Kafka } = require('kafkajs'); - -async function consumeFromKafka() { - const consumer = kafka.consumer({ groupId: 'chat-processor' }); - await consumer.connect(); - await consumer.subscribe({ topic: 'ably-chat-messages' }); - - await consumer.run({ - eachMessage: async ({ message }) => { - const envelope = JSON.parse(message.value); - const roomName = extractRoomName(envelope.channel); - const decoded = decodeMessages(envelope); - - await processMessages(roomName, decoded); - } - }); -} -``` - - -## Monitoring integration performance - -Monitor the health and performance of your integrations in realtime using Ably's [metachannels](/docs/metadata-stats/metadata/subscribe). Metachannels provide app-level metadata about integrations, statistics, and errors. - -### Monitor integration errors - -The [`[meta]log` channel](/docs/metadata-stats/metadata/subscribe#log) publishes error events from integrations in realtime. This enables you to detect and respond to integration failures as they occur. - -Subscribe to the `[meta]log` channel to receive error events: - - -```javascript -const Ably = require('ably'); -const client = new Ably.Realtime({ key: 'YOUR_API_KEY' }); - -const metaLogChannel = client.channels.get('[meta]log'); - -await metaLogChannel.subscribe((message) => { - if (message.data.error) { - alertOperationsTeam(message.data); - } -}); -``` - - -Integration error logs will contain a `tag` field as part of the `data` payload, indicating the integration type - for example, `reactor.generic.http`, for webhooks. This allows you to filter and categorize errors by integration method. - -**Note:** The `[meta]log` channel only publishes errors that cannot be directly reported to clients. For example, webhook delivery failures or queue publishing errors. - -### Monitor app statistics - -The [`[meta]stats:minute` channel](/docs/metadata-stats/metadata/subscribe#stats) publishes [app-level statistics](/docs/metadata-stats/stats) every minute. Use these statistics to monitor integration throughput, message volumes, and resource usage. - -Subscribe to receive [statistics](docs/metadata-stats/stats#outbound) updates: - - -```javascript -const statsChannel = client.channels.get('[meta]stats:minute'); - -await statsChannel.subscribe('update', (event) => { - const stats = event.data.entries; - - // Monitor integration-specific metrics - console.log('Integration stats:', { - webhooks: stats['messages.outbound.webhook.messages.count'] || 0, - queues: stats['messages.outbound.sharedQueue.messages.count'] || 0, - // Add other metrics as needed - }); - - // Alert on anomalies - if (stats['messages.outbound.webhook.all.failed'] > threshold) { - alertOnWebhookFailures(stats); - } -}); -``` - - -Use the [rewind channel option](/docs/channels/options/rewind) to retrieve the most recent statistics immediately: - - -```javascript -// Get the last statistics event plus subscribe to future updates -const statsChannel = client.channels.get('[meta]stats:minute', { - params: { rewind: '1' } -}); - -statsChannel.subscribe('update', (event) => { - console.log('Stats:', event.data.entries); -}); -``` - - -### Integration monitoring best practices - -* **Subscribe to both channels**: Use `[meta]log` for error detection and `[meta]stats:minute` for performance monitoring -* **Set up alerting**: Create automated alerts for integration failures or throughput anomalies -* **Track trends**: Store statistics over time to identify patterns and capacity planning needs -* **Filter by integration type**: Use the statistics entries to monitor specific integration methods (webhooks, queues, streaming) -* **Correlate with external logs**: Use `requestId` from error events to correlate with your system logs - -See [metadata subscriptions documentation](/docs/metadata-stats/metadata/subscribe) for complete details on available metachannels. - -## Handling message reactions - -Message reactions are delivered as separate events with `action: 'message.summary'`. - -### Reaction summary structure - - -```javascript -{ - "action": "message.summary", - "serial": "original-message-serial", - "annotations": { - "summary": { - "reaction:unique.v1": { - "👍": { "count": 5 }, - "❤️": { "count": 3 } - } - } - } -} -``` - - -### Processing reactions - - -```javascript -function processMessage(message) { - if (message.action === 'message.summary') { - // Handle reaction summary - const reactions = message.annotations?.summary?.['reaction:unique.v1'] || {}; - updateReactionCounts(message.serial, reactions); - return; - } - - // Handle regular message - saveMessage(message); -} -``` - - -Read more about [message reactions](/docs/chat/rooms/message-reactions) and the [reactions annotation mapping](/docs/chat/integrations#how-to-handle-message-reactions). - - -## Related documentation - -* [Chat integrations](/docs/chat/integrations) - Technical reference for message structure mapping -* [Platform integrations](/docs/platform/integrations) - Complete integration setup guides -* [Message structure](/docs/chat/rooms/messages#structure) - Chat message format details -* [Chat history](/docs/chat/rooms/history) - Retrieve historical messages via API -* [Export chat data guide](/docs/guides/chat/export-chat) - Guide for long-term storage use cases -* [External integrations guide](/docs/guides/chat/external-integrations) - Guide for processing and responding to messages