Developer Docs
Welcome! Everything you need to send push notifications, track delivery, and build realtime applications with Datonow.
smart_toyUsing an AI coding assistant?
Point it at /agents.md — optimized for LLM context
01Install SDKs
npm install @datonow/sdk @datonow/sdk-server@datonow/sdkBrowser & Node.js client — WebSocket, channels, notification inbox.
@datonow/sdk-serverServer-only — send notifications, trigger events, sign channel auth.
02Send Push Notifications (Server)
Messages are stored in PostgreSQL, delivered instantly over WebSocket, and queued for offline clients.
import { DatonowServer } from '@datonow/sdk-server';
const server = new DatonowServer({
appId: process.env.DATONOW_APP_ID!,
key: process.env.DATONOW_APP_KEY!,
secret: process.env.DATONOW_APP_SECRET!, // app secret = broadcast to all tokens
});
// Send a push notification (broadcast)
await server.sendNotification({
title: 'Deploy Complete',
message: 'v2.1.0 deployed to production',
priority: 8, // 1 (low) – 10 (critical)
ttl: 3600,
extras: { buildId: 1482, url: 'https://ci.example.com/builds/1482' },
});
// getMessages() with app secret → MessageWithStats[] (aggregate delivery counts)
const { messages, nextCursor } = await server.getMessages({ limit: 20 });
// messages[0] = { id, title, priority, totalDeliveries: 3, deliveredCount: 2, readCount: 1 }
// Token management
const token = await server.createClientToken('ios-device-alice');
await server.deleteClientToken(token.id);03Receive Notifications (Client)
Connect with a client token to enter notification mode. Offline messages are automatically delivered on reconnect (exponential backoff, max 5 attempts).
import { DatonowClient } from '@datonow/sdk';
// Connect with a client token (notification mode)
const client = new DatonowClient({
token: process.env.DATONOW_CLIENT_TOKEN!,
wsHost: 'wss://rt.yourdomain.com',
httpHost: 'https://rt.yourdomain.com',
});
client.connect();
// Receive realtime push notifications
client.notifications.onNotification((msg) => {
console.log(`[P${msg.priority}] ${msg.title}: ${msg.message}`);
});
// Inbox history — only messages delivered to THIS token, with per-token status
const { messages, nextCursor } = await client.notifications.getHistory(
undefined, // cursor — omit for first page
20 // limit (max 100)
);
// messages[0] = { id, title, priority, status: "pending"|"delivered"|"read", deliveredAt?, readAt? }
await client.notifications.markAsRead(messages[0].id);
await client.notifications.markAllRead();
await client.notifications.delete(messages[0].id);04Realtime Channels
Subscribe to public, private, or presence channels for live event broadcasting.
Public
my-channelAnyone with the App Key. No auth needed.
Private
private-*HMAC-signed auth from your server required.
Presence
presence-*Like private + tracks online members.
Client
import { DatonowClient } from '@datonow/sdk';
// AppKey mode — pub/sub channels (like Pusher)
const client = new DatonowClient({
appKey: process.env.DATONOW_APP_KEY!,
wsHost: 'wss://rt.yourdomain.com',
authEndpoint: '/api/datonow/auth', // required for private/presence
});
client.connect();
// Public channel (no auth needed)
const ch = client.subscribe('public-updates');
await ch.subscribe();
ch.bind('news_alert', (data) => console.log('Event:', data));
// Presence channel (tracks who is online)
const presence = client.subscribe('presence-room-42');
await presence.subscribe({ userId: 'u-123', userInfo: { name: 'Alice' } });
presence.bind('datonow:member_added', (m) => console.log('Joined:', m));
presence.bind('datonow:member_removed', (m) => console.log('Left:', m));Server
import { DatonowServer } from '@datonow/sdk-server';
const server = new DatonowServer({
appId: process.env.DATONOW_APP_ID!,
key: process.env.DATONOW_APP_KEY!,
secret: process.env.DATONOW_APP_SECRET!,
});
// Trigger event on one channel
await server.trigger('public-updates', 'news_alert', { message: 'Hello!' });
// Trigger on multiple channels at once
await server.trigger(['room-1', 'room-2'], 'announcement', { text: 'Maintenance in 5 min' });
// Exclude sender socket (avoid echo)
await server.trigger('chat', 'message_sent', data, { socketId: senderSocketId });05Private & Presence Auth Endpoint
When a client subscribes to a private-* or presence-* channel, the SDK POSTs to your authEndpoint with the socket ID and channel name. Your server verifies the session and returns an HMAC signature.
// app/api/datonow/auth/route.ts (Next.js App Router)
import { NextRequest, NextResponse } from 'next/server';
import { DatonowServer } from '@datonow/sdk-server';
import { getSession } from '@/lib/session'; // your auth helper
const server = new DatonowServer({
appId: process.env.DATONOW_APP_ID!,
key: process.env.DATONOW_APP_KEY!,
secret: process.env.DATONOW_APP_SECRET!,
});
export async function POST(req: NextRequest) {
const session = await getSession();
if (!session) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
const { socket_id, channel_name } = await req.json();
// Presence channel — includes user identity (member list)
if (channel_name.startsWith('presence-')) {
const auth = server.authenticatePresenceChannel(
socket_id, channel_name,
session.userId, { name: session.userName }
);
return NextResponse.json(auth); // { auth, channel_data }
}
// Private channel — just sign it
if (channel_name.startsWith('private-')) {
return NextResponse.json(server.authenticatePrivateChannel(socket_id, channel_name));
}
return NextResponse.json({ error: 'Unknown channel type' }, { status: 400 });
}06SaaS Integration — User-Targeted Notifications
The key pattern for integrating Datonow into your own product: one client token per end-user. When you authenticate with a client token, delivery is scoped to that token only. When you authenticate with the app secret, it broadcasts to all tokens in the app.
Auth mode determines delivery scope
secret: appSecret → broadcast to all tokens · secret: userToken → targeted to one user
// Step 1 — On user signup in YOUR app, create a Datonow token for them
const mgmt = new DatonowServer({ appId, key, secret: process.env.DATONOW_APP_SECRET! });
const token = await mgmt.createClientToken(`user-${user.id}`);
await db.users.update({ where: { id: user.id }, data: { datonowToken: token.token } });
// Step 2 — When a background job finishes, notify that specific user
const user = await db.users.findUnique({ where: { id: jobResult.userId } });
const targeted = new DatonowServer({
appId, key,
secret: user.datonowToken, // ← user's personal token, NOT the app secret
});
await targeted.sendNotification({
title: 'Your export is ready',
message: 'The CSV you requested has finished processing.',
priority: 7,
extras: { downloadUrl: 'https://...' },
});
// Step 3 — Connect from the browser (each user connects with their own token)
const { token } = await fetch('/api/me/notifications-token').then(r => r.json());
const client = new DatonowClient({ token, wsHost, httpHost });
client.connect();
client.notifications.onNotification((msg) => showToast(msg.title, msg.message));07REST API Reference
Notification API
X-Datonow-App-Id + X-Datonow-Token/api/message/api/messages?cursor=&limit=/api/messages/{id}/api/messages/api/messages/{id}/delivered/api/messages/{id}/read/api/messages/read-all/api/apps/{appId}/tokens/api/apps/{appId}/tokens/api/apps/{appId}/tokens/{id}Realtime API
X-Datonow-Signature (HMAC)/ws?token=/ws?appKey=/api/trigger/api/subscribe/api/unsubscribe10 conn/s per IP WebSocket connections600 req/min per app Trigger / message APIUsing Cursor, Copilot, or Claude?
/agents.md is a plain-text integration guide optimized for AI coding assistants — accurate method signatures, type definitions, auth scoping rules, and copy-paste patterns.