Skip to main content

Python bindings for the Zoom RTMS C SDK - Real-Time Media Streaming

Project description

Zoom Realtime Media Streams (RTMS) SDK

Bindings for real-time audio, video, and transcript streams from Zoom Meetings

npm PyPI docs

Supported Products

The RTMS SDK works with multiple Zoom products:

See examples/ for complete guides and code samples.

Platform Support Status

Language Status Supported Platforms
Node.js ✅ Supported darwin-arm64, linux-x64
Python ✅ Supported darwin-arm64, linux-x64
Go 📅 Planned -

We are actively working to expand both language and platform support in future releases.

Overview

The RTMS SDK allows developers to:

  • Connect to live Zoom meetings
  • Process real-time media streams (audio, video, transcripts)
  • Receive events about session and participant updates
  • Build applications that interact with Zoom meetings in real-time
  • Handle webhook events with full control over validation and responses

Installation

Node.js

⚠️ Requirements: Node.js >= 20.3.0 (Node.js 24 LTS recommended)

The RTMS SDK uses N-API versions 9 and 10, which require Node.js 20.3.0 or higher.

# Check your Node.js version
node --version

# Install the package
npm install @zoom/rtms

If you're using an older version of Node.js:

# Using nvm (recommended)
nvm install 24       # Install Node.js 24 LTS (recommended)
nvm use 24

# Or install Node.js 20 LTS (minimum)
nvm install 20
nvm use 20

# Reinstall the package
npm install @zoom/rtms

Download Node.js: https://nodejs.org/

The Node.js package provides both class-based and singleton APIs for connecting to RTMS streams.

Python

⚠️ Requirements: Python >= 3.10 (Python 3.10, 3.11, 3.12, or 3.13)

The RTMS SDK requires Python 3.10 or higher.

# Check your Python version
python3 --version

# Install from PyPI
pip install rtms

If you're using an older version of Python:

# Using pyenv (recommended)
pyenv install 3.12
pyenv local 3.12

# Or using your system's package manager
# Ubuntu/Debian: sudo apt install python3.12
# macOS: brew install python@3.12

Download Python: https://www.python.org/downloads/

The Python package provides a Pythonic decorator-based API with full feature parity to Node.js.

For Contributors

This project uses Task (go-task) for development builds and testing.

If you're an end user installing via npm or pip, you don't need Task - the installation will work automatically using prebuilt binaries.

If you're a contributor building from source, you'll need to install Task:

macOS:

brew install go-task

Linux:

sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b ~/.local/bin

Quick Start for Contributors:

# Verify your environment meets requirements
task doctor

# Setup the project (fetch SDK, install dependencies)
task setup

# Build for your platform
task build:js          # Build Node.js bindings
task build:py          # Build Python bindings

# Run tests
task test:js           # Test Node.js
task test:py           # Test Python

# See all available commands
task --list

For detailed contribution guidelines, build instructions, and troubleshooting, see CONTRIBUTING.md.

Usage

Speaker Identification with Mixed Audio

When using AUDIO_MIXED_STREAM (the default), all participants are mixed into a single audio stream and the audio callback's metadata will not identify the current speaker. To identify who is speaking, register an onActiveSpeakerEvent callback — it fires whenever the active speaker changes. See Troubleshooting #7 and #80 for details.

Node.js - Webhook Integration

Easily respond to Zoom webhooks and connect to RTMS streams:

import rtms from "@zoom/rtms";

// CommonJS
// const rtms = require('@zoom/rtms').default;

rtms.onWebhookEvent(({event, payload}) => {
    if (event !== "meeting.rtms_started") return;

    const client = new rtms.Client();
    
    client.onAudioData((data, timestamp, metadata) => {
        console.log(`Received audio: ${data.length} bytes from ${metadata.userName}`);
    });

    client.join(payload);
});

Node.js - Advanced Webhook Handling

For advanced use cases requiring custom webhook validation or response handling (e.g., Zoom's webhook validation challenge), you can use the enhanced callback with raw HTTP access:

import rtms from "@zoom/rtms";

rtms.onWebhookEvent((payload, req, res) => {
    // Access request headers for webhook validation
    const signature = req.headers['x-zoom-signature'];
    
    // Handle Zoom's webhook validation challenge
    if (req.headers['x-zoom-webhook-validator']) {
        const validationToken = req.headers['x-zoom-webhook-validator'];
        
        // Echo back the validation token
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ plainToken: validationToken }));
        return;
    }
    
    // Custom validation logic
    if (!validateWebhookSignature(payload, signature)) {
        res.writeHead(401, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ error: 'Invalid signature' }));
        return;
    }
    
    // Process the webhook payload
    if (payload.event === "meeting.rtms_started") {
        const client = new rtms.Client();
        
        client.onAudioData((data, timestamp, metadata) => {
            console.log(`Received audio from ${metadata.userName}`);
        });
        
        client.join(payload.payload);
    }
    
    // Send custom response
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ status: 'ok' }));
});

Node.js - Sharing Ports with Existing Servers

If you need to integrate webhook handling with your existing Express, Fastify, or other HTTP server (useful for Cloud Run, Kubernetes, or any deployment requiring a single port), use createWebhookHandler:

import express from 'express';
import rtms from '@zoom/rtms';

const app = express();
app.use(express.json());

// Your existing application routes
app.get('/health', (req, res) => {
    res.json({ status: 'healthy' });
});

app.get('/admin', (req, res) => {
    res.json({ admin: 'panel' });
});

// Create a webhook handler that can be mounted on your existing server
const webhookHandler = rtms.createWebhookHandler(
    (payload) => {
        console.log(`Received webhook: ${payload.event}`);

        if (payload.event === "meeting.rtms_started") {
            const client = new rtms.Client();
            client.onAudioData((data, timestamp, metadata) => {
                console.log(`Audio from ${metadata.userName}`);
            });
            client.join(payload.payload);
        }
    },
    '/zoom/webhook'  // Path to handle
);

// Mount the webhook handler on your Express app
app.post('/zoom/webhook', webhookHandler);

// Single port for all routes
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
    console.log(`Server running on port ${PORT}`);
    console.log(`Webhook endpoint: http://localhost:${PORT}/zoom/webhook`);
    console.log(`Health check: http://localhost:${PORT}/health`);
});

You can also use RawWebhookCallback with createWebhookHandler for custom validation:

const webhookHandler = rtms.createWebhookHandler(
    (payload, req, res) => {
        // Custom validation with raw HTTP access
        const signature = req.headers['x-zoom-signature'];

        if (!validateSignature(payload, signature)) {
            res.writeHead(401);
            res.end('Unauthorized');
            return;
        }

        // Process webhook...

        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ status: 'ok' }));
    },
    '/zoom/webhook'
);

app.post('/zoom/webhook', webhookHandler);

Node.js - Class-Based Approach

For greater control or connecting to multiple streams simultaneously:

import rtms from "@zoom/rtms";

const client = new rtms.Client();

client.onAudioData((data, timestamp, metadata) => {
    console.log(`Received audio: ${data.length} bytes`);
});

client.join({
    meeting_uuid: "your_meeting_uuid",
    rtms_stream_id: "your_stream_id",
    server_urls: "wss://example.zoom.us",
});

Node.js - Global Singleton

When you only need to connect to a single RTMS stream:

import rtms from "@zoom/rtms";

rtms.onAudioData((data, timestamp, metadata) => {
    console.log(`Received audio from ${metadata.userName}`);
});

rtms.join({
    meeting_uuid: "your_meeting_uuid",
    rtms_stream_id: "your_stream_id",
    server_urls: "wss://rtms.zoom.us"
});

Python - Basic Usage

#!/usr/bin/env python3
import rtms
import signal
import sys
from dotenv import load_dotenv

load_dotenv()

client = rtms.Client()

# Graceful shutdown handler
def signal_handler(sig, frame):
    print('\nShutting down gracefully...')
    client.leave()
    sys.exit(0)

signal.signal(signal.SIGINT, signal_handler)

# Webhook event handler
@client.on_webhook_event()
def handle_webhook(payload):
    if payload.get('event') == 'meeting.rtms_started':
        rtms_payload = payload.get('payload', {})
        client.join(
            meeting_uuid=rtms_payload.get('meeting_uuid'),
            rtms_stream_id=rtms_payload.get('rtms_stream_id'),
            server_urls=rtms_payload.get('server_urls'),
            signature=rtms_payload.get('signature')
        )

# Callback handlers
@client.onJoinConfirm
def on_join(reason):
    print(f'Joined meeting: {reason}')

@client.onTranscriptData
def on_transcript(data, size, timestamp, metadata):
    text = data.decode('utf-8')
    print(f'[{metadata.userName}]: {text}')

@client.onLeave
def on_leave(reason):
    print(f'Left meeting: {reason}')

if __name__ == '__main__':
    print('Webhook server running on http://localhost:8080')
    import time
    while True:
        # Process queued join requests from webhook thread
        client._process_join_queue()
        # Poll for SDK events
        client._poll_if_needed()
        time.sleep(0.01)

Python - Advanced Webhook Validation

For production use cases requiring custom webhook validation:

import rtms
import hmac
import hashlib

client = rtms.Client()

@client.on_webhook_event()
def handle_webhook(payload, request, response):
    # Access request headers for validation
    signature = request.headers.get('x-zoom-signature')

    # Handle Zoom's webhook validation challenge
    if request.headers.get('x-zoom-webhook-validator'):
        validator = request.headers['x-zoom-webhook-validator']
        response.set_status(200)
        response.send({'plainToken': validator})
        return

    # Custom signature validation
    if not validate_signature(payload, signature):
        response.set_status(401)
        response.send({'error': 'Invalid signature'})
        return

    # Process valid webhook
    if payload.get('event') == 'meeting.rtms_started':
        client.join(payload.get('payload'))

    response.send({'status': 'ok'})

Python - Environment Setup

Create a virtual environment and install dependencies:

# Create virtual environment
python3 -m venv .venv

# Activate virtual environment
source .venv/bin/activate  # On Windows: .venv\Scripts\activate

# Install dependencies
pip install python-dotenv

# Install RTMS SDK
pip install rtms

Create a .env file:

# Required - Your Zoom OAuth credentials
ZM_RTMS_CLIENT=your_client_id
ZM_RTMS_SECRET=your_client_secret

# Optional - Webhook server configuration
ZM_RTMS_PORT=8080
ZM_RTMS_PATH=/webhook

# Optional - Logging configuration
ZM_RTMS_LOG_LEVEL=debug          # error, warn, info, debug, trace
ZM_RTMS_LOG_FORMAT=progressive    # progressive or json
ZM_RTMS_LOG_ENABLED=true          # true or false

Building from Source

The RTMS SDK can be built from source using either Docker (recommended) or local build tools.

Using Docker (Recommended)

Prerequisites

  • Docker and Docker Compose
  • Zoom RTMS C SDK files (contact Zoom for access)
  • Task installed (or use Docker's Task installation)

Steps

# Clone the repository
git clone https://github.com/zoom/rtms.git
cd rtms

# Place your SDK library files in the lib/{arch} folder
# For linux-x64:
cp ../librtmsdk.0.2025xxxx/librtmsdk.so.0 lib/linux-x64

# For darwin-arm64 (Apple Silicon):
cp ../librtmsdk.0.2025xxxx/librtmsdk.dylib lib/darwin-arm64

# Place the include files in the proper directory
cp ../librtmsdk.0.2025xxxx/h/* lib/include

# Build using Docker Compose with Task
docker compose run --rm build task build:js    # Build Node.js for linux-x64
docker compose run --rm build task build:py    # Build Python wheel for linux-x64

# Or use convenience services
docker compose run --rm test-js                # Build and test Node.js
docker compose run --rm test-py                # Build and test Python

Docker Compose creates distributable packages for linux-x64 (prebuilds for Node.js, wheels for Python). Use this when developing on macOS to build Linux packages for distribution.

Building Locally

Prerequisites

  • Node.js (>= 20.3.0, LTS recommended)
  • Python 3.10+ with pip (for Python build)
  • CMake 3.25+
  • C/C++ build tools
  • Task (go-task) - https://taskfile.dev/installation/
  • Zoom RTMS C SDK files (contact Zoom for access)

Steps

# Install system dependencies
## macOS
brew install cmake go-task python@3.13 node@24

## Linux
sudo apt update
sudo apt install -y cmake python3-full python3-pip npm
sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b ~/.local/bin

# Clone and set up the repository
git clone https://github.com/zoom/rtms.git
cd rtms

# Place SDK files in the appropriate lib directory
# lib/linux-x64/ or lib/darwin-arm64/

# Verify your environment meets requirements
task doctor

# Setup project (fetches SDK if not present, installs dependencies)
task setup

# Build for specific language and platform
task build:js             # Build Node.js for current platform
task build:js:linux       # Build Node.js for Linux (via Docker)
task build:js:darwin      # Build Node.js for macOS

task build:py             # Build Python for current platform
task build:py:linux       # Build Python wheel for Linux (via Docker)
task build:py:darwin      # Build Python wheel for macOS

Development Commands

The project uses Task (go-task) for build orchestration. Commands follow the pattern: task <action>:<lang>:<platform>

# See all available commands
task --list

# Verify environment
task doctor                   # Check Node, Python, CMake, Docker versions

# Setup project
task setup                    # Fetch SDK and install dependencies

# Build modes
BUILD_TYPE=Debug task build:js    # Build in debug mode
BUILD_TYPE=Release task build:js  # Build in release mode (default)

# Debug logging for C SDK callbacks
RTMS_DEBUG=ON task build:js       # Enable verbose callback logging

Troubleshooting

If you encounter issues:

1. Segmentation Fault / Crash on Startup

Symptoms:

  • Immediate crash when requiring/importing the module
  • Error message: Segmentation fault (core dumped)
  • Stack trace shows napi_module_register_by_symbol

Root Cause: Using Node.js version < 20.3.0

Solution:

# 1. Check your Node.js version
node --version

# 2. If < 20.3.0, upgrade to a supported version

# Using nvm (recommended):
nvm install 24           # Install Node.js 24 LTS (recommended)
nvm use 24

# Or install minimum version:
nvm install 20
nvm use 20

# Or download from: https://nodejs.org/

# 3. Clear npm cache and reinstall
npm cache clean --force
rm -rf node_modules package-lock.json
npm install

Prevention:

  • Always use Node.js 20.3.0 or higher
  • Use recommended version with .nvmrc: nvm use (Node.js 24 LTS)
  • Check version before installing: node --version

2. Platform Support

Verify you're using a supported platform (darwin-arm64 or linux-x64)

3. SDK Files

Ensure RTMS C SDK files are correctly placed in the appropriate lib directory

4. Build Mode

Try both debug and release modes (npm run debug or npm run release)

5. Dependencies

Verify all prerequisites are installed

6. Audio Defaults Mismatch

This SDK uses different default audio parameters than the raw RTMS WebSocket protocol for better out-of-the-box quality. If you need to match the WebSocket protocol defaults, see #92 for details.

7. Identifying Speakers with Mixed Audio Streams

When using AUDIO_MIXED_STREAM, the audio callback's metadata does not identify the current speaker since all participants are mixed into a single stream. To identify who is speaking, use the onActiveSpeakerEvent callback:

Node.js:

client.onActiveSpeakerEvent((timestamp, userId, userName) => {
    console.log(`Active speaker: ${userName} (${userId})`);
});

Python:

@client.onActiveSpeakerEvent
def on_active_speaker(timestamp, user_id, user_name):
    print(f"Active speaker: {user_name} ({user_id})")

This callback notifies your application whenever the active speaker changes in the meeting. You can also use the lower-level onEventEx function with the active speaker event type directly. See #80 for more details.

For Maintainers

If you're a maintainer looking to build, test, or publish new releases of the RTMS SDK, please refer to PUBLISHING.md for comprehensive documentation on:

  • Building platform-specific wheels and prebuilds
  • Publishing to npm and PyPI
  • GitHub Actions CI/CD workflow
  • Testing procedures
  • Troubleshooting common issues
  • Release workflows for Node.js and Python

License

This project is licensed under the MIT License - see the LICENSE.md file for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

rtms-1.0.3-cp313-cp313-manylinux_2_34_x86_64.whl (5.4 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.34+ x86-64

rtms-1.0.3-cp313-cp313-macosx_11_0_arm64.whl (8.3 MB view details)

Uploaded CPython 3.13macOS 11.0+ ARM64

rtms-1.0.3-cp312-cp312-manylinux_2_34_x86_64.whl (5.4 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.34+ x86-64

rtms-1.0.3-cp312-cp312-macosx_11_0_arm64.whl (8.3 MB view details)

Uploaded CPython 3.12macOS 11.0+ ARM64

rtms-1.0.3-cp311-cp311-manylinux_2_34_x86_64.whl (5.4 MB view details)

Uploaded CPython 3.11manylinux: glibc 2.34+ x86-64

rtms-1.0.3-cp311-cp311-macosx_11_0_arm64.whl (8.3 MB view details)

Uploaded CPython 3.11macOS 11.0+ ARM64

rtms-1.0.3-cp310-cp310-manylinux_2_34_x86_64.whl (5.4 MB view details)

Uploaded CPython 3.10manylinux: glibc 2.34+ x86-64

rtms-1.0.3-cp310-cp310-macosx_11_0_arm64.whl (8.3 MB view details)

Uploaded CPython 3.10macOS 11.0+ ARM64

File details

Details for the file rtms-1.0.3-cp313-cp313-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for rtms-1.0.3-cp313-cp313-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 d0957367d0dfe60435d81e335f9fa888cde089c50de00240e4cb566b386b41d6
MD5 8ba82321fe8c9543645c197655d9dee3
BLAKE2b-256 38f75a28b5f3dd9c3748535a04208d9910227282fd5c6c45933a7b4034e34546

See more details on using hashes here.

Provenance

The following attestation bundles were made for rtms-1.0.3-cp313-cp313-manylinux_2_34_x86_64.whl:

Publisher: publish.yml on zoom/rtms

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file rtms-1.0.3-cp313-cp313-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for rtms-1.0.3-cp313-cp313-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 109b0be1026f32e8e040f34d76a2f7af02166b6dddc18a3354f753fabcea1658
MD5 00557410f0558d7ab8e4436403e3eb7b
BLAKE2b-256 81193e28769848e732ed4dcc59373692457298c5211d5106e5c71a00a89159be

See more details on using hashes here.

Provenance

The following attestation bundles were made for rtms-1.0.3-cp313-cp313-macosx_11_0_arm64.whl:

Publisher: publish.yml on zoom/rtms

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file rtms-1.0.3-cp312-cp312-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for rtms-1.0.3-cp312-cp312-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 79cd1486449f8a90633f9ac733365a90f8f4317d1aaa00e7d91dcc80d97f6767
MD5 41cc09b3ad7ff9129c7ac7c809857d10
BLAKE2b-256 8fa9c9ac05232b23368046ed61866c15a31af8ac2e411af9fa868a38171b3956

See more details on using hashes here.

Provenance

The following attestation bundles were made for rtms-1.0.3-cp312-cp312-manylinux_2_34_x86_64.whl:

Publisher: publish.yml on zoom/rtms

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file rtms-1.0.3-cp312-cp312-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for rtms-1.0.3-cp312-cp312-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 30bd52257da9308dfe4597e03ed1158f198398d88f64000c9729f88d5198a32e
MD5 7f1fe50bc8f9f592a5516e00d99dfbb9
BLAKE2b-256 d114a1b25dc12dd744c14d6e3e72fb9ca3eb3f96a791f34bf3a9897dbfd4aca9

See more details on using hashes here.

Provenance

The following attestation bundles were made for rtms-1.0.3-cp312-cp312-macosx_11_0_arm64.whl:

Publisher: publish.yml on zoom/rtms

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file rtms-1.0.3-cp311-cp311-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for rtms-1.0.3-cp311-cp311-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 6857f87d3e5b3c7c7b2b261bd13b257ca7dfe86ca2e1a55c50bc1bcf869855fe
MD5 5d95b5cb12dd7ccfb81525be21449144
BLAKE2b-256 e3fa567445a5c1b79caf3552d3519f9cdc1890ebde78671b57e30066ee79af41

See more details on using hashes here.

Provenance

The following attestation bundles were made for rtms-1.0.3-cp311-cp311-manylinux_2_34_x86_64.whl:

Publisher: publish.yml on zoom/rtms

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file rtms-1.0.3-cp311-cp311-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for rtms-1.0.3-cp311-cp311-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 18a7579d00ed80741d4daca72f51a73fefadd892173b8720c9bbe5b0527101bd
MD5 fdac9fb6fb2cf64283038d0f83efcec3
BLAKE2b-256 50debc4487f48a8b24576098ff8e751a1498ffa5ca9285b3eac4f70b4555faad

See more details on using hashes here.

Provenance

The following attestation bundles were made for rtms-1.0.3-cp311-cp311-macosx_11_0_arm64.whl:

Publisher: publish.yml on zoom/rtms

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file rtms-1.0.3-cp310-cp310-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for rtms-1.0.3-cp310-cp310-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 a303a9ab1a4d87b0d2b694cb0163f759e8e0586a8bc56330a16c6bff09b0e3f5
MD5 8a93e322fc0e5d5727834cdaf2ec4741
BLAKE2b-256 3c367c67ec6136198be3e51ec0c9373633576ac03e0a5d8efbe8c52ec5a73570

See more details on using hashes here.

Provenance

The following attestation bundles were made for rtms-1.0.3-cp310-cp310-manylinux_2_34_x86_64.whl:

Publisher: publish.yml on zoom/rtms

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file rtms-1.0.3-cp310-cp310-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for rtms-1.0.3-cp310-cp310-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 e254b9d9f24b45cd1e3cd0ab80f9a4ea653800bc217370a106a9b63e3769639e
MD5 2062d7225857a15027864eab877219a1
BLAKE2b-256 af3870aa5483605ebf5eacf0e7af93fb82076e820c92535096e5ddfaa542cb4a

See more details on using hashes here.

Provenance

The following attestation bundles were made for rtms-1.0.3-cp310-cp310-macosx_11_0_arm64.whl:

Publisher: publish.yml on zoom/rtms

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page