Skip to main content

Self-Hosting Guide

Deploy PocketPing on your own infrastructure for complete control over your data.

Architecture Overview​

A self-hosted PocketPing setup consists of three components:

ComponentDescription
Chat WidgetEmbedded on your website, connects via HTTP + SSE
Bridge ServerRoutes messages between widget and platforms
Messaging PlatformsTelegram, Discord, or Slack for notifications

Option 1: Minimal Setup​

The simplest self-hosted setup uses the Python or Node.js SDK with embedded bridge support. No separate bridge server needed.

npm install @pocketping/sdk-node
server.js
const express = require('express');
const { PocketPing, TelegramBridge } = require('@pocketping/sdk-node');

const app = express();
const pp = new PocketPing({
bridges: [new TelegramBridge(process.env.TELEGRAM_BOT_TOKEN, process.env.TELEGRAM_CHAT_ID)],
});

// Mount routes
app.use('/pocketping', pp.middleware());

app.listen(8000);

Option 2: Full Setup with Bridge Server​

For production or when you want to use multiple bridges, run the bridge server separately using Docker.

1. Deploy Bridge Server​

The bridge server is written in Go and provides HTTP-only communication with messaging platforms.

docker-compose.yml
services:
bridge:
image: ghcr.io/pocketping/pocketping-bridge:latest
ports:
- "3001:3001"
environment:
- TELEGRAM_BOT_TOKEN=your_token
- TELEGRAM_CHAT_ID=your_chat_id
- DISCORD_BOT_TOKEN=your_discord_token
- DISCORD_CHANNEL_ID=your_channel_id
restart: unless-stopped

# Run: docker compose up -d

2. Point the Widget at the Bridge Server​

The standalone bridge server is self-contained — there is no separate backend or SDK in between. Your widget connects to it directly via its endpoint. Expose the bridge on a public HTTPS URL (e.g. behind a reverse proxy) and use that URL:

<script src="https://cdn.pocketping.io/widget.js"></script>
<script>
PocketPing.init({
endpoint: 'https://bridge.yourdomain.com', // your bridge server URL
operatorName: 'Support',
});
</script>

That's it — open your site, send a message, and it appears in your messaging platform.

Storage Options​

By default, sessions and messages are stored in memory using MemoryStorage. This works for development but data is lost on restart.

Built-in Storage​

StorageIncludedDescription
MemoryStorage✅ YesIn-memory, data lost on restart. Good for dev/testing.
PostgresStorage❌ NoImplement yourself using the interface below.
RedisStorage❌ NoImplement yourself using the interface below.

Custom Storage Interface​

To persist data, implement the Storage interface:

import { Storage } from '@pocketping/sdk-node';

class MyStorage implements Storage {
async createSession(session: Session): Promise<void>;
async getSession(sessionId: string): Promise<Session | null>;
async updateSession(session: Session): Promise<void>;
async deleteSession(sessionId: string): Promise<void>;
async saveMessage(message: Message): Promise<void>;
async getMessages(sessionId: string, after?: string, limit?: number): Promise<Message[]>;
async getMessage(messageId: string): Promise<Message | null>;
}

Example: PostgreSQL​

note

This is an example. PostgresStorage is not included in the SDK—you need to implement it.

import { Storage, Session, Message } from '@pocketping/sdk-node';
import { Pool } from 'pg';

class PostgresStorage implements Storage {
private pool: Pool;

constructor(connectionString: string) {
this.pool = new Pool({ connectionString });
}

async createSession(session: Session): Promise<void> {
await this.pool.query(
'INSERT INTO sessions (id, visitor_id, created_at, last_activity) VALUES ($1, $2, $3, $4)',
[session.id, session.visitorId, session.createdAt, session.lastActivity]
);
}

async getSession(sessionId: string): Promise<Session | null> {
const result = await this.pool.query(
'SELECT * FROM sessions WHERE id = $1',
[sessionId]
);
if (result.rows.length === 0) return null;
const row = result.rows[0];
return {
id: row.id,
visitorId: row.visitor_id,
createdAt: row.created_at,
lastActivity: row.last_activity,
// ... other fields
};
}

// ... implement remaining methods
}

// Usage
const storage = new PostgresStorage('postgresql://user:pass@localhost/pocketping');

const pp = new PocketPing({
storage,
bridges: [new TelegramBridge(process.env.TELEGRAM_BOT_TOKEN, process.env.TELEGRAM_CHAT_ID)],
});

Deployment Checklist​

  • Backend deployed with SSL (HTTPS)
  • Bridge server deployed (Docker or embedded)
  • At least one bridge configured (Telegram/Discord/Slack)
  • Widget added to frontend
  • CORS configured (backend allows widget domain)
  • Persistent storage configured (optional but recommended)
  • Health checks and monitoring in place

Environment Variables​

VariableRequiredDescription
PORTNoBridge server port (default: 3001)
API_KEYNoSecret key for API authentication
TELEGRAM_BOT_TOKENIf using TelegramBot token from BotFather
TELEGRAM_CHAT_IDIf using TelegramTelegram supergroup ID (starts with -100)
DISCORD_BOT_TOKENIf using DiscordDiscord bot token
DISCORD_CHANNEL_IDIf using DiscordDiscord channel ID for threads
EVENTS_WEBHOOK_URLNoURL to forward custom events (Zapier, Make, n8n)
EVENTS_WEBHOOK_SECRETNoSecret for HMAC-SHA256 signature verification

Webhook Integration​

Forward custom events to external services for automation:

docker-compose.yml
services:
bridge:
image: ghcr.io/pocketping/pocketping-bridge:latest
ports:
- "3001:3001"
environment:
- TELEGRAM_BOT_TOKEN=your_token
- TELEGRAM_CHAT_ID=your_chat_id
# Forward events to Zapier, Make, n8n, etc.
- EVENTS_WEBHOOK_URL=https://hooks.zapier.com/hooks/catch/123456/abcdef
- EVENTS_WEBHOOK_SECRET=your_secret_key # Optional

See Node.js SDK - Webhook Forwarding for payload structure and signature verification.

Next Steps​