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:
| Component | Description |
|---|---|
| Chat Widget | Embedded on your website, connects via HTTP + SSE |
| Bridge Server | Routes messages between widget and platforms |
| Messaging Platforms | Telegram, 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.
- Node.js
- Python
- Go
- PHP
- Ruby
npm install @pocketping/sdk-node
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);
pip install pocketping
from fastapi import FastAPI
from pocketping import PocketPing
from pocketping.bridges import TelegramBridge
app = FastAPI()
pp = PocketPing(
bridge=TelegramBridge(
token="YOUR_BOT_TOKEN",
chat_id="YOUR_CHAT_ID"
),
)
# Mount routes at /pocketping
pp.mount_fastapi(app, prefix="/pocketping")
# Run: uvicorn main:app --host 0.0.0.0 --port 8000
go get github.com/Ruwad-io/pocketping/sdk-go
package main
import (
"net/http"
"os"
pocketping "github.com/Ruwad-io/pocketping/sdk-go"
)
func main() {
pp := pocketping.New(pocketping.Config{
Bridge: pocketping.NewTelegramBridge(pocketping.TelegramConfig{
Token: os.Getenv("TELEGRAM_BOT_TOKEN"),
ChatID: os.Getenv("TELEGRAM_CHAT_ID"),
}),
})
http.Handle("/pocketping/", pp.Handler("/pocketping"))
http.ListenAndServe(":8000", nil)
}
composer require pocketping/sdk
<?php
use PocketPing\PocketPing;
use PocketPing\Bridges\TelegramBridge;
$pp = new PocketPing([
'bridge' => new TelegramBridge([
'token' => getenv('TELEGRAM_BOT_TOKEN'),
'chat_id' => getenv('TELEGRAM_CHAT_ID'),
]),
]);
// Mount the SDK handlers under /pocketping
Route::any('/pocketping/{path?}', fn ($path = '') => $pp->handle($path));
gem install pocketping
require 'pocketping'
pp = PocketPing.new(
bridge: PocketPing::Bridges::TelegramBridge.new(
token: ENV['TELEGRAM_BOT_TOKEN'],
chat_id: ENV['TELEGRAM_CHAT_ID']
)
)
Rails.application.routes.draw do
mount pp.rack_app => '/pocketping'
end
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.
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
| Storage | Included | Description |
|---|---|---|
MemoryStorage | ✅ Yes | In-memory, data lost on restart. Good for dev/testing. |
PostgresStorage | ❌ No | Implement yourself using the interface below. |
RedisStorage | ❌ No | Implement yourself using the interface below. |
Custom Storage Interface
To persist data, implement the Storage interface:
- Node.js
- Python
- Go
- PHP
- Ruby
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>;
}
from pocketping.storage import Storage
class MyStorage(Storage):
async def create_session(self, session: Session) -> None: ...
async def get_session(self, session_id: str) -> Session | None: ...
async def update_session(self, session: Session) -> None: ...
async def delete_session(self, session_id: str) -> None: ...
async def save_message(self, message: Message) -> None: ...
async def get_messages(self, session_id: str, after: str | None = None, limit: int = 50) -> list[Message]: ...
async def get_message(self, message_id: str) -> Message | None: ...
type Storage interface {
CreateSession(ctx context.Context, session *Session) error
GetSession(ctx context.Context, id string) (*Session, error)
UpdateSession(ctx context.Context, session *Session) error
DeleteSession(ctx context.Context, id string) error
SaveMessage(ctx context.Context, sessionID string, msg *Message) error
GetMessages(ctx context.Context, sessionID string, after string, limit int) ([]*Message, error)
GetMessage(ctx context.Context, id string) (*Message, error)
}
interface StorageInterface {
public function createSession(array $session): void;
public function getSession(string $id): ?array;
public function updateSession(array $session): void;
public function deleteSession(string $id): void;
public function saveMessage(string $sessionId, array $message): void;
public function getMessages(string $sessionId, ?string $after = null, int $limit = 50): array;
public function getMessage(string $id): ?array;
}
# Implement these methods in your storage class
def create_session(session); end
def get_session(id); end
def update_session(session); end
def delete_session(id); end
def save_message(session_id, message); end
def get_messages(session_id, after: nil, limit: 50); end
def get_message(id); end
Example: PostgreSQL
This is an example. PostgresStorage is not included in the SDK—you need to implement it.
- Node.js
- Python
- Go
- PHP
- Ruby
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)],
});
from pocketping.storage import Storage
from pocketping.models import Session, Message
import asyncpg
class PostgresStorage(Storage):
def __init__(self, dsn: str):
self.dsn = dsn
self.pool = None
async def connect(self):
self.pool = await asyncpg.create_pool(self.dsn)
async def create_session(self, session: Session) -> None:
async with self.pool.acquire() as conn:
await conn.execute('''
INSERT INTO sessions (id, visitor_id, created_at, last_activity)
VALUES ($1, $2, $3, $4)
''', session.id, session.visitor_id, session.created_at, session.last_activity)
async def get_session(self, session_id: str) -> Session | None:
async with self.pool.acquire() as conn:
row = await conn.fetchrow('SELECT * FROM sessions WHERE id = $1', session_id)
if row:
return Session(id=row['id'], visitor_id=row['visitor_id'], ...)
return None
# ... implement remaining methods
# Usage
storage = PostgresStorage("postgresql://user:pass@localhost/pocketping")
await storage.connect()
pp = PocketPing(
storage=storage,
bridge=TelegramBridge(...)
)
import (
"database/sql"
_ "github.com/lib/pq"
)
type PostgresStorage struct {
db *sql.DB
}
func NewPostgresStorage(connStr string) (*PostgresStorage, error) {
db, err := sql.Open("postgres", connStr)
if err != nil {
return nil, err
}
return &PostgresStorage{db: db}, nil
}
func (s *PostgresStorage) CreateSession(ctx context.Context, session *pocketping.Session) error {
_, err := s.db.ExecContext(ctx,
`INSERT INTO sessions (id, visitor_id, created_at) VALUES ($1, $2, $3)`,
session.ID, session.VisitorID, session.CreatedAt)
return err
}
// ... implement remaining methods
// Usage
storage, _ := NewPostgresStorage("postgresql://user:pass@localhost/pocketping")
pp := pocketping.New(pocketping.Config{
Storage: storage,
Bridge: pocketping.NewTelegramBridge(pocketping.TelegramConfig{
Token: os.Getenv("TELEGRAM_BOT_TOKEN"),
ChatID: os.Getenv("TELEGRAM_CHAT_ID"),
}),
})
class PostgresStorage implements StorageInterface {
private PDO $pdo;
public function __construct(string $dsn) {
$this->pdo = new PDO($dsn);
}
public function createSession(array $session): void {
$stmt = $this->pdo->prepare(
'INSERT INTO sessions (id, visitor_id, created_at) VALUES (?, ?, ?)'
);
$stmt->execute([$session['id'], $session['visitorId'], $session['createdAt']]);
}
// ... implement remaining methods
}
// Usage
$storage = new PostgresStorage('pgsql:host=localhost;dbname=pocketping');
$pp = new PocketPing([
'storage' => $storage,
'bridge' => new TelegramBridge([
'token' => getenv('TELEGRAM_BOT_TOKEN'),
'chat_id' => getenv('TELEGRAM_CHAT_ID'),
]),
]);
require 'pg'
class PostgresStorage
def initialize(connection_string)
@conn = PG.connect(connection_string)
end
def create_session(session)
@conn.exec_params(
'INSERT INTO sessions (id, visitor_id, created_at) VALUES ($1, $2, $3)',
[session[:id], session[:visitor_id], session[:created_at]]
)
end
# ... implement remaining methods
end
# Usage
storage = PostgresStorage.new('postgresql://user:pass@localhost/pocketping')
pp = PocketPing.new(
storage: storage,
bridge: PocketPing::Bridges::TelegramBridge.new(
token: ENV['TELEGRAM_BOT_TOKEN'],
chat_id: 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
| Variable | Required | Description |
|---|---|---|
PORT | No | Bridge server port (default: 3001) |
API_KEY | No | Secret key for API authentication |
TELEGRAM_BOT_TOKEN | If using Telegram | Bot token from BotFather |
TELEGRAM_CHAT_ID | If using Telegram | Telegram supergroup ID (starts with -100) |
DISCORD_BOT_TOKEN | If using Discord | Discord bot token |
DISCORD_CHANNEL_ID | If using Discord | Discord channel ID for threads |
EVENTS_WEBHOOK_URL | No | URL to forward custom events (Zapier, Make, n8n) |
EVENTS_WEBHOOK_SECRET | No | Secret for HMAC-SHA256 signature verification |
Webhook Integration
Forward custom events to external services for automation:
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
- Docker Setup - Detailed Docker deployment guide
- AI Fallback - Configure AI auto-responses
- API Reference - Complete REST API documentation