Server SDK
Trigger events, sign channel joins, push notifications, and drive chat from your backend.
pingerchips-js-server (v3.0.0) is the Node.js server SDK. It triggers events,
mints capability tokens for browser clients, sends push
notifications, reads and writes
Durable Objects, and drives the
Chat REST API.
Upgrading from 2.x? Auth moved to X-App-Key / X-App-Secret headers and
capability tokens. authenticate() now returns a token instead of an
{appKey}:{hmac} string, and authenticateChat() calls POST /api/auth. See
Migrating from 2.x.
The Python package is agent-only — no trigger, no channel auth, no push.
See Python below.
Install
npm install pingerchips-js-serverNode ≥ 18 (uses native fetch). ESM only.
Initialise
import PingerchipsServer from 'pingerchips-js-server';
const pc = new PingerchipsServer(appKey, appSecret, options);Arguments are positional: (appKey, appSecret, options?).
| Option | Type | Default | Description |
|---|---|---|---|
endpoint | string | https://queue.pingerchips.com (prod) / http://localhost:4000 (dev) | API base URL. Also read from PINGERCHIPS_API_ENDPOINT. |
requestTimeout | number | 10000 | Per-request timeout (ms). |
retries | number | 2 | Retries on 5xx / network errors. |
mtls | object | — | Mutual TLS — see mTLS. |
Trigger (HTTP)
await pc.trigger(channel, event, data);| Arg | Constraint |
|---|---|
channel | ≤ 200 chars |
event | ≤ 200 chars |
data | JSON-serialisable, ≤ 64 KB |
Invalid input throws a TypeError before any network call. Non-2xx throws.
await pc.trigger('announcements', 'new-post', { title: 'v2 released', url: '/blog/v2' });
await pc.trigger(`private-user-${userId}`, 'notification', { type: 'order-shipped' });How it authenticates
trigger POSTs to POST /api/apps/{appKey}/trigger with X-App-Key +
X-App-Secret headers. No credentials in the body:
{ channel, event, data }. Every server call in this SDK uses the same header
auth — see Authentication.
Response: { success: true, message: "Event triggered", app_id }.
triggerBatch
await pc.triggerBatch(['room-a', 'room-b', 'room-c'], 'update', { value: 42 });Fires the same event on each channel; resolves to an array of per-channel results.
Channel authentication
Mint a capability token for a private/presence join from your auth endpoint.
pc.authenticate(socketId, channelName, userData?)| Arg | Notes |
|---|---|
socketId | From the client (client.getSocketId() / the socket_id in the auth request body). |
channelName | Must start with private- or presence-. |
userData | Presence only — must include user_id. |
Returns (synchronously — no network call):
// private caps: channel:subscribe:{channelName}
{ auth: "{appKey}.{payloadB64}.{sigB64}" }
// private with user data
{ auth: "{appKey}.{payloadB64}.{sigB64}", user_data: "{...json...}" }
// presence caps: channel:subscribe:{channelName}, channel:presence:{channelName}
{ auth: "{appKey}.{payloadB64}.{sigB64}", channel_data: "{...json...}" }auth is a fast-path capability token bound to socketId. Its sigB64 is
base64url( HMAC-SHA256( appSecret, "{appKey}.{payloadB64}" ) ) — full layout
in Authentication.
Many grants on one token
pc.authorize(socketId, {
channels: ['orders'],
presence: ['lobby'],
objects: [{ type: 'order', key: 'order-42' }],
threads: ['thread-abc'],
}, clientId);
// → { auth } — one fast-path token covering every listed grantAuth endpoint
import express from 'express';
import PingerchipsServer from 'pingerchips-js-server';
const app = express();
app.use(express.json());
const pc = new PingerchipsServer(process.env.PINGERCHIPS_APP_KEY, process.env.PINGERCHIPS_APP_SECRET);
app.post('/pingerchips/auth', (req, res) => {
const { socket_id, channel_name, auth_info } = req.body;
const user = verifyUser(auth_info?.token);
if (!user) return res.status(403).json({ error: 'Unauthorized' });
// authorize this specific channel
if (channel_name.startsWith('private-') && channel_name !== `private-user-${user.id}`) {
return res.status(403).json({ error: 'Access denied' });
}
let userData = null;
if (channel_name.startsWith('presence-')) {
userData = { user_id: user.id, user_info: { name: user.name, avatar: user.avatarUrl } };
}
res.json(pc.authenticate(socket_id, channel_name, userData));
});
app.listen(3000);The client SDK calls this automatically for private-* / presence-* channels
when you set authEndpoint.
authenticate throws if channelName isn't private-/presence- prefixed, or
if a presence userData lacks user_id.
Chat auth token
Issue a JWT capability token for a browser client to join a chat thread. Calls
POST /api/auth with X-App-Key + X-App-Secret.
// full client verb set for the thread
const token = await pc.authenticateChat(socketId, threadId, clientId);
// { auth: "<jwt>" }
// narrowed — read-only
await pc.authenticateChat(socketId, threadId, clientId, ['subscribe']);The 4th argument is an optional list of chat verbs to narrow the grant —
short ('subscribe', 'publish:user_message', 'cancel_own',
'tool_approval') or fully-qualified ('chat:tool_approval:thread-abc'). Omit
it for the full client set. See
Chat authentication.
For an arbitrary grant set as a JWT (rather than a fast token), use
pc.issueToken(socketId, clientId, grants) — same grants shape as
authorize.
Push notifications
// only if the user has no active connection
await pc.notify('user-123', { title: 'New message', body: 'Alice said hi' });
// always deliver
await pc.notify(
'user-123',
{ title: 'Alert', body: 'Action required' },
{ trigger: 'always', data: { url: '/alerts' } },
);Register a device token from your server after your app collects it:
// mobile
await pc.registerPushToken('user-123', {
deviceId: 'device-abc',
platform: 'fcm', // 'fcm' | 'web_fcm' | 'apns' | 'web'
token: 'FCM_TOKEN',
});
// web push
await pc.registerPushToken('user-123', {
deviceId: 'browser-xyz',
platform: 'web',
endpoint: 'https://fcm.googleapis.com/...',
p256dh: '...',
auth: '...',
});
await pc.unregisterPushToken('user-123', 'device-abc');Durable Objects
const order = pc.object('order', 'order-42');
await order.set('status', 'shipped'); // X-App-Key / X-App-Secret headers
// browser subscription token
const { auth } = pc.authenticateObject(socketId, 'order', 'order-42');
// caps: object:read:order/order-42Full API on the Durable Objects Server SDK page.
Channel config builders
Presets for a channel's durability behaviour, passed when configuring a flow via the API:
import { channelConfig, durableChannelConfig, ephemeralChannelConfig } from 'pingerchips-js-server';
channelConfig({ durable: true, replay: true, maxReplayMessages: 500, ordering: 'strict' });
// → { durable, replay, max_replay_messages, ordering, flow_engine }
durableChannelConfig(); // L2: WAL + strict ordering + full replay
ephemeralChannelConfig(); // L0: no WAL, no replay, best-effortChat REST API
PingerchipsServerChat — threads, participants, messages, and bots from your
backend. Uses X-App-Key + X-App-Secret.
import { PingerchipsServerChat } from 'pingerchips-js-server';
const chat = new PingerchipsServerChat(
process.env.PINGERCHIPS_APP_KEY,
process.env.PINGERCHIPS_APP_SECRET,
);
// threads
const thread = await chat.createThread({ title: 'Support #42', createdBy: 'user-1' });
await chat.assignBot(thread.id, 'support-bot');
await chat.resolveThread(thread.id);
await chat.archiveThread(thread.id);
await chat.handoffThread(thread.id, 'agent-99');
// participants
await chat.joinThread(thread.id, { userId: 'user-1', role: 'member' });
await chat.markRead(thread.id, participantId);
// messages
await chat.sendMessage(thread.id, { userId: 'user-1', content: { text: 'Hello!' } });
const msgs = await chat.listMessages(thread.id, { page: 1 });
await chat.editMessage(thread.id, msgs[0].id, { text: 'Hello, edited' });Every method returns the unwrapped { id, ...attributes }. Full resource list:
Chat architecture — JSON:API.
Building on the Vercel AI SDK? @pingerchips/ai's createAgentSession wraps
the agent flow with an Invocation model and a Next.js route helper — see the
chat quickstart.
mTLS
const pc = new PingerchipsServer(APP_KEY, APP_SECRET, {
mtls: {
enabled: true,
cert: '/path/to/client-cert.pem', // path or PEM string
key: '/path/to/client-key.pem',
ca: '/path/to/ca-cert.pem',
rejectUnauthorized: true,
},
});The self-hosted server exposes mTLS on port 4001 when MTLS_ENABLED=true.
Error handling
try {
await pc.trigger('my-channel', 'my-event', { text: 'Hello' });
} catch (err) {
// "channel must be a non-empty string"
// "data payload must be 64KB or less"
// "Failed to trigger event: 429 Too Many Requests - Rate limit exceeded"
// "Request timeout after 10000ms"
}5xx and network errors are retried up to retries times before throwing.
Migrating from 2.x
| 2.x | 3.0.0 |
|---|---|
trigger / push sent app_id + app_secret in the body | X-App-Key + X-App-Secret headers; body carries only the payload |
Durable Objects HTTP signed each request (X-Signature / X-Timestamp) | X-App-Key + X-App-Secret headers |
authenticate() → { auth: "{appKey}:{hmac}" } | { auth: "{appKey}.{payloadB64}.{sigB64}" } — a capability token. The client SDK forwards it unchanged. |
authenticateChat() → POST /api/chat/auth, 4th arg = raw capability strings | POST /api/auth, 4th arg = chat verbs to narrow |
authenticateObject() → { auth: "{appKey}:{hmac}" } | a capability token granting object:read:{type}/{key} |
| — | new: authorize() (fast-path, many grants), issueToken() (JWT, many grants), exported mintFastToken() |
new PingerchipsServer(k, s, { token }) | the token option is removed |
If you only call trigger / notify, no code change is needed — just upgrade.
If you run an auth endpoint, its output shape changed but the client SDK handles
the new token transparently, so no client change is needed either.
The backend cut over completely — a 2.x server SDK cannot authenticate against the current backend. Upgrade server and client together.
Python
The pingerchips package (v0.1.0) is an agent SDK — AgentSession, Run,
Invocation for driving chat threads. It has no trigger, no channel auth, no
push, no Durable Objects client.
- Chat agents from Python: Agent SDK
- Pub/sub or push from Python: call the HTTP endpoints directly
Next steps
- Client SDK — receive events in the browser
- Channel Types — public, private, presence
- Durable Objects
- Chat