Examples
These examples demonstrate common robot integration patterns. All examples assume you have already created a robot and have your API key and webhook secret ready.
Webhook Bot (Node.js)
A complete Express server that receives webhook events, verifies signatures, and responds to messages.
import express from 'express';
import crypto from 'crypto';
const app = express();
app.use(express.raw({ type: 'application/json' }));
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET; // whsec_...
const API_KEY = process.env.ROBOT_API_KEY; // lag_robot_...
const API_BASE = 'https://api.trylag.com/robots';
function verifySignature(payload, headers) {
const msgId = headers['webhook-id'];
const timestamp = headers['webhook-timestamp'];
const signature = headers['webhook-signature'];
// Decode the whsec_ prefixed secret
const secretBytes = Buffer.from(
WEBHOOK_SECRET.replace('whsec_', ''),
'base64'
);
// Construct the signed content
const signedContent = `${msgId}.${timestamp}.${payload}`;
// Compute HMAC-SHA256
const computed = crypto
.createHmac('sha256', secretBytes)
.update(signedContent)
.digest('base64');
// Compare with provided signatures
const expected = `v1,${computed}`;
const signatures = signature.split(' ');
return signatures.some((sig) => sig === expected);
}
app.post('/webhook', (req, res) => {
const payload = req.body.toString();
if (!verifySignature(payload, req.headers)) {
return res.status(401).send('Invalid signature');
}
const event = JSON.parse(payload);
console.log(`Received event: ${event.type}`);
switch (event.type) {
case 'room.message':
handleMessage(event);
break;
case 'member.join':
handleMemberJoin(event);
break;
default:
console.log(`Unhandled event type: ${event.type}`);
}
res.status(200).send('OK');
});
async function handleMessage(event) {
const { serverId, data } = event;
const { roomId, content, senderName } = data;
// Respond to !ping command
if (content === '!ping') {
await sendMessage(serverId, roomId, `Pong! Hi ${senderName}`);
}
}
async function handleMemberJoin(event) {
const { serverId, data } = event;
const welcomeRoomId = 'your-welcome-room-id';
await sendMessage(
serverId,
welcomeRoomId,
`Welcome to the server, ${data.username}!`
);
}
async function sendMessage(serverId, roomId, content) {
const url = `${API_BASE}/servers/${serverId}/rooms/${roomId}/messages`;
const response = await fetch(url, {
method: 'POST',
headers: {
'Authorization': `Robot ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ content }),
});
if (!response.ok) {
console.error(`Failed to send message: ${response.status}`);
}
}
app.listen(3000, () => {
console.log('Webhook server listening on port 3000');
}); SSE Listener
An SSE client that connects to the Lag event stream and processes events in real time.
const API_KEY = process.env.ROBOT_API_KEY;
const SSE_URL = 'https://api.trylag.com/robots/@me/events/sse';
let lastEventId = null;
function connect() {
const headers = {
'Authorization': `Robot ${API_KEY}`,
'Accept': 'text/event-stream',
};
if (lastEventId) {
headers['Last-Event-ID'] = lastEventId;
}
fetch(SSE_URL, { headers })
.then((response) => {
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
function read() {
reader.read().then(({ done, value }) => {
if (done) {
console.log('SSE connection closed, reconnecting...');
setTimeout(connect, 1000);
return;
}
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('
');
buffer = lines.pop() || '';
let currentId = null;
let currentData = null;
for (const line of lines) {
if (line.startsWith('id: ')) {
currentId = line.slice(4);
} else if (line.startsWith('data: ')) {
currentData = line.slice(6);
} else if (line === '' && currentData) {
// End of event
if (currentId) lastEventId = currentId;
try {
const event = JSON.parse(currentData);
handleEvent(event);
} catch (err) {
console.error('Failed to parse event:', err);
}
currentId = null;
currentData = null;
}
}
read();
});
}
read();
})
.catch((err) => {
console.error('SSE connection error:', err);
setTimeout(connect, 5000);
});
}
function handleEvent(event) {
console.log(`[${event.type}]`, JSON.stringify(event.data));
// Add your event handling logic here
}
connect(); Long-Poll Client
A simple long-poll loop that fetches pending events.
const API_KEY = process.env.ROBOT_API_KEY;
const POLL_URL = 'https://api.trylag.com/robots/@me/events/poll';
async function pollLoop() {
while (true) {
try {
const response = await fetch(POLL_URL, {
headers: {
'Authorization': `Robot ${API_KEY}`,
},
});
if (!response.ok) {
console.error(`Poll failed: ${response.status}`);
await sleep(5000);
continue;
}
const { events } = await response.json();
for (const event of events) {
handleEvent(event);
}
// If we got a full batch, poll again immediately
if (events.length === 50) {
continue;
}
} catch (err) {
console.error('Poll error:', err);
await sleep(5000);
}
}
}
function handleEvent(event) {
console.log(`[${event.type}]`, JSON.stringify(event.data));
}
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
pollLoop(); Webhook Signature Verification
Node.js
import crypto from 'crypto';
function verifyWebhookSignature(payload, headers, secret) {
const msgId = headers['webhook-id'];
const timestamp = headers['webhook-timestamp'];
const signatures = headers['webhook-signature'];
// Reject old timestamps (5 minute tolerance)
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - parseInt(timestamp)) > 300) {
return false;
}
const secretBytes = Buffer.from(secret.replace('whsec_', ''), 'base64');
const signedContent = `${msgId}.${timestamp}.${payload}`;
const computed = crypto
.createHmac('sha256', secretBytes)
.update(signedContent)
.digest('base64');
const expected = `v1,${computed}`;
return signatures.split(' ').some((sig) => sig === expected);
} Python
import hmac
import hashlib
import base64
import time
def verify_webhook_signature(payload, headers, secret):
msg_id = headers.get('webhook-id')
timestamp = headers.get('webhook-timestamp')
signatures = headers.get('webhook-signature')
# Reject old timestamps (5 minute tolerance)
now = int(time.time())
if abs(now - int(timestamp)) > 300:
return False
# Decode the whsec_ prefixed secret
secret_bytes = base64.b64decode(secret.removeprefix('whsec_'))
# Construct signed content
signed_content = f'{msg_id}.{timestamp}.{payload}'
# Compute HMAC-SHA256
computed = hmac.new(
secret_bytes,
signed_content.encode('utf-8'),
hashlib.sha256
).digest()
expected = f'v1,{base64.b64encode(computed).decode()}'
return expected in signatures.split(' ') Sending a Message as a Robot
A standalone example of sending a message to a room.
const API_KEY = 'lag_robot_abc_yoursecretkey';
const SERVER_ID = 'your-server-id';
const ROOM_ID = 'your-room-id';
async function sendRobotMessage(content) {
const url = `https://api.trylag.com/robots/@me/servers/${SERVER_ID}/rooms/${ROOM_ID}/messages`;
const response = await fetch(url, {
method: 'POST',
headers: {
'Authorization': `Robot ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ content }),
});
if (!response.ok) {
const error = await response.json();
console.error('Failed to send message:', error);
return null;
}
const { message } = await response.json();
console.log(`Message sent: ${message.id}`);
return message;
}
// Usage
sendRobotMessage('Hello from my robot!');