Webhooks
Webhooks allow your external systems to react to NoLag events. Configure hydration webhooks to pre-populate state when actors subscribe, and trigger webhooks to notify your backend when messages are published.
Key Concept
Webhooks are configured at the App level. All rooms within an app share the same webhook configuration.
Webhook Types
Hydration Webhook
Called when an actor subscribes to a topic. Use this to pre-populate the actor's state with current data (e.g., recent messages, current game state, latest prices).
Request Format
{
"actorId": "act_xxx",
"roomName": "general-chat",
"topicName": "messages"
}Response
Return any JSON data. This will be forwarded to the subscribing actor as a hydration message.
{
"recentMessages": [
{ "from": "alice", "text": "Hello!", "timestamp": 1234567890 },
{ "from": "bob", "text": "Hi there!", "timestamp": 1234567891 }
],
"participantCount": 5
}Client Receives
room.on('hydration', (topic, data) => {
console.log('Hydration data for', topic, data)
// { recentMessages: [...], participantCount: 5 }
})Trigger Webhook
Called when an actor publishes data to a topic. Use this to trigger external workflows, store messages, send notifications, or integrate with third-party services.
Request Format
{
"roomName": "general-chat",
"topicName": "messages",
"actorId": "act_xxx",
"data": {
"text": "Hello everyone!",
"timestamp": 1234567890
}
}Response
Return any 2xx status code to acknowledge receipt. The response body is ignored.
Configuration
Via Dashboard
- Navigate to your App settings
- Find the "Webhooks" section
- Enter the webhook URL and optional headers
- Save changes
Via API
// Configure webhooks via API
await nolag.apps.update('app_xxx', {
hydrationWebhook: {
url: 'https://api.example.com/nolag/hydration',
headers: {
'Authorization': 'Bearer your-token'
}
},
triggerWebhook: {
url: 'https://api.example.com/nolag/trigger',
headers: {
'X-Custom-Header': 'value'
}
}
})Authentication
Webhooks support two authentication methods:
- Query Parameters - Add auth tokens directly in the URL:
https://api.example.com/webhook?api_key=xxx - Request Headers - Add custom headers like
Authorizationor API key headers
Security Note
Dead Letter Queue (DLQ)
When a webhook call fails after all retry attempts, the failed request is stored in the Dead Letter Queue. You can view and retry these failed requests from the dashboard.
Retry Behavior
- Webhooks are retried up to 3 times with exponential backoff
- Server errors (5xx) and network errors trigger retries
- Client errors (4xx) do not trigger retries
- Timeout is 30 seconds per request
DLQ Entry Contents
Each DLQ entry contains:
- Original webhook URL
- Request headers and body
- Response status code (if received)
- Error message
- Timestamp and retry count
Viewing DLQ via API
// List failed webhooks for your organization
const dlqEntries = await nolag.webhookDlq.list({
status: 'pending',
limit: 50
})
// Retry a specific entry
await nolag.webhookDlq.retry('dlq_xxx')Best Practices
- Respond quickly - Webhook handlers should respond within a few seconds. Use async processing for heavy work.
- Idempotency - Design your handlers to be idempotent since retries may cause duplicate calls.
- Logging - Log incoming webhook requests for debugging and auditing.
- Monitoring - Monitor your DLQ and set up alerts for failed webhooks.
Example: Chat Application
Here's a complete webhook handler example for a chat application:
import express from 'express'
const app = express()
app.use(express.json())
// Hydration: Return recent messages when user joins
app.post('/nolag/hydration', async (req, res) => {
const { actorId, roomName, topicName } = req.body
const messages = await db.messages.findMany({
where: { room: roomName },
orderBy: { createdAt: 'desc' },
take: 50
})
res.json({ messages: messages.reverse() })
})
// Trigger: Store message and send notifications
app.post('/nolag/trigger', async (req, res) => {
const { roomName, topicName, actorId, data } = req.body
await db.messages.create({
data: { room: roomName, actorId, content: data.text }
})
await pushService.notifyRoom(roomName, data)
res.status(200).send('OK')
})