Pingerchips LogoPingerchips
SDK Reference

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-server

Node ≥ 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?).

OptionTypeDefaultDescription
endpointstringhttps://queue.pingerchips.com (prod) / http://localhost:4000 (dev)API base URL. Also read from PINGERCHIPS_API_ENDPOINT.
requestTimeoutnumber10000Per-request timeout (ms).
retriesnumber2Retries on 5xx / network errors.
mtlsobjectMutual TLS — see mTLS.

Trigger (HTTP)

await pc.trigger(channel, event, data);
ArgConstraint
channel≤ 200 chars
event≤ 200 chars
dataJSON-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?)
ArgNotes
socketIdFrom the client (client.getSocketId() / the socket_id in the auth request body).
channelNameMust start with private- or presence-.
userDataPresence 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 grant

Auth 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-42

Full 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-effort

Chat 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.x3.0.0
trigger / push sent app_id + app_secret in the bodyX-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 stringsPOST /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 SDKAgentSession, 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

On this page