Skip to main content
Use Upstash Realtime on the server to emit events, subscribe to channels, and retrieve message history.

Emit Events

Emit events from any server context:
route.ts
import { realtime } from "@/lib/realtime"

export const POST = async () => {
  await realtime.emit("notification.alert", "hello world!")

  return new Response("OK")
}
Emit to specific channels:
route.ts
import { realtime } from "@/lib/realtime"

export const POST = async () => {
  const channel = realtime.channel("user-123")
  await channel.emit("notification.alert", "hello world!")

  return new Response("OK")
}

Subscribe to Events

Subscribe to events on a channel:
route.ts
import { realtime } from "@/lib/realtime"

const unsubscribe = await realtime.channel("notifications").subscribe({
  events: ["notification.alert"],
  onData({ event, data, channel }) {
    console.log("New notification:", data)
  },
})
Subscribe to multiple events:
route.ts
import { realtime } from "@/lib/realtime"

const unsubscribe = await realtime.channel("room-123").subscribe({
  events: ["chat.message", "user.joined", "user.left"],
  onData({ event, data, channel }) {
    // 👇 data is automatically typed based on the event
    if (event === "chat.message") console.log("New message:", data)
    if (event === "user.joined") console.log("User joined:", data)
    if (event === "user.left") console.log("User left:", data)
  },
})

Unsubscribe

Clean up subscriptions when done:
route.ts
import { realtime } from "@/lib/realtime"

const channel = realtime.channel("room-123")

const unsubscribe = await channel.subscribe({
  events: ["chat.message"],
  onData({ data }) {
    console.log("Message:", data)
  },
})

unsubscribe()
// or: channel.unsubscribe()

Retrieve History

Fetch past messages from a channel:
route.ts
import { realtime } from "@/lib/realtime"

export const GET = async () => {
  const messages = await realtime.channel("room-123").history()

  return new Response(JSON.stringify(messages))
}

History Options

limit
number
default:"1000"
Maximum number of messages to retrieve (capped at 1000)
start
number
Fetch messages after this Unix timestamp (in milliseconds)
end
number
Fetch messages before this Unix timestamp (in milliseconds)
route.ts
const messages = await realtime.channel("room-123").history({
  limit: 100,
  start: Date.now() - 86400000,
})

History Response

Each history message contains:
type HistoryMessage = {
  id: string
  event: string
  channel: string
  data: unknown
}

Subscribe with History

Replay past messages and continue subscribing to new ones:
route.ts
import { realtime } from "@/lib/realtime"

const channel = realtime.channel("room-123")

await channel.subscribe({
  events: ["chat.message"],
  history: true,
  onData({ event, data, channel }) {
    console.log("Message:", data)
  },
})
Pass history options:
route.ts
await channel.subscribe({
  events: ["chat.message"],
  history: {
    limit: 50,
    start: Date.now() - 3600000,
  },
  onData({ data }) {
    console.log("Message:", data)
  },
})
This pattern:
  1. Fetches messages matching the history criteria
  2. Replays them in chronological order
  3. Continues to listen for new messages

Use Cases

Stream progress updates from long-running tasks:
app/api/job/route.ts
import { realtime } from "@/lib/realtime"

export const POST = async (req: Request) => {
  const { jobId } = await req.json()
  const channel = realtime.channel(jobId)

  await channel.emit("job.started", { progress: 0 })

  for (let i = 0; i <= 100; i += 10) {
    await processChunk()
    await channel.emit("job.progress", { progress: i })
  }

  await channel.emit("job.completed", { progress: 100 })

  return new Response("OK")
}
Process events with server-side logic:
route.ts
import { realtime } from "@/lib/realtime"
import { sendEmail } from "@/lib/email"

await realtime.channel("notifications").subscribe({
  events: ["notification.alert"],
  onData: async ({ data }) => {
    if (data.priority === "high") {
      await sendEmail({
        to: data.userId,
        subject: "Urgent Notification",
        body: data.message,
      })
    }
  },
})
Emit events to multiple channels:
route.ts
import { realtime } from "@/lib/realtime"

export const POST = async (req: Request) => {
  const { teamIds, message } = await req.json()

  await Promise.all(
    teamIds.map((teamId: string) =>
      realtime.channel(`team-${teamId}`).emit("announcement", message)
    )
  )

  return new Response("Broadcast sent")
}
Forward webhook events to realtime channels:
app/api/webhook/route.ts
import { realtime } from "@/lib/realtime"

export const POST = async (req: Request) => {
  const payload = await req.json()

  const channel = realtime.channel(`user-${payload.userId}`)
  await channel.emit("webhook.received", payload)

  return new Response("OK")
}

Next Steps