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

  1. Navigate to your App settings
  2. Find the "Webhooks" section
  3. Enter the webhook URL and optional headers
  4. 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 Authorization or API key headers

Security Note

Always use HTTPS for webhook URLs. Webhook headers are stored encrypted at rest.

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')
})