# Server-side subscriptions

> Read Realtime messages in server code, forward them over Server-Sent Events, or react to them in a worker.

Subscribe from your server when a browser can't hold a WebSocket, when you want to forward updates through an existing HTTP response, or when another service needs to react to a function's progress. Server code that has your Inngest client authenticates with its signing key, so it doesn't need a subscription token. In Go, `realtime.Subscribe()` takes a subscription token instead, and the Python SDK doesn't support subscribing yet.

## Read a stream

`inngest.realtime.subscribe()` returns a readable stream of typed messages. Iterate it with `for await`.

```ts {{ title: "TypeScript" }}
import { inngest } from "./inngest/client";
import { jobChannel } from "./inngest/channels";

const stream = await inngest.realtime.subscribe({
  channel: jobChannel({ jobId: "job_123" }),
  topics: ["status", "result"],
});

for await (const message of stream) {
  if (message.kind === "run") continue;
  console.log(message.topic, message.data);
}
```

```go {{ title: "Go" }}
import (
	"context"
	"fmt"

	"github.com/inngest/inngestgo/realtime"
)

// token is a subscription token for the "job:job_123" channel and its
// "status" and "result" topics. The Go SDK can't mint tokens, so mint it
// with the TypeScript or Python SDK.
func readJobUpdates(ctx context.Context, token string) error {
	stream, err := realtime.Subscribe(ctx, token)
	if err != nil {
		return err
	}

	for item := range stream {
		if item.IsErr() {
			return item.Err()
		}
		if !item.IsMessage() {
			continue
		}
		msg := item.Message()
		if msg.Kind == "run" {
			continue
		}
		fmt.Println(msg.Topic, string(msg.Data))
	}
	return nil
}
```

`subscribe({ app: inngest, channel, topics })` from `inngest/realtime` does the same when you pass the client as an argument.

## Handle messages with a callback

Pass `onMessage` to receive each message in a callback instead of a stream. The call returns a subscription handle. Pass `onError` to catch connection errors.

```ts {{ title: "TypeScript" }}
const subscription = await inngest.realtime.subscribe({
  channel: jobChannel({ jobId: "job_123" }),
  topics: ["status"],
  onMessage: (message) => {
    console.log(message.data);
  },
  onError: (err) => {
    console.error("Realtime subscription failed", err);
  },
});

// Later, when you no longer need updates:
subscription.close();
```

```go {{ title: "Go" }}
import (
	"context"
	"fmt"
	"log"

	"github.com/inngest/inngestgo/realtime"
)

func watchJobStatus(token string) (stop func(), err error) {
	ctx, cancel := context.WithCancel(context.Background())

	stream, err := realtime.Subscribe(ctx, token)
	if err != nil {
		cancel()
		return nil, err
	}

	go func() {
		for item := range stream {
			switch {
			case item.IsErr():
				log.Println("Realtime subscription failed", item.Err())
			case item.IsMessage():
				fmt.Println(string(item.Message().Data))
			}
		}
	}()

	// Later, when you no longer need updates, call stop().
	// Canceling the context closes the connection and the channel.
	return cancel, nil
}
```

Close every subscription when its consumer finishes so the connection doesn't stay open.

## Forward updates with Server-Sent Events

`getEncodedStream()` returns bytes you can return as a `text/event-stream` response. This route starts a function and streams its updates back in the same response.

```ts {{ title: "TypeScript", filename: "app/api/hello-world/route.ts" }}
import { inngest } from "@/inngest/client";
import { helloChannel } from "@/inngest/channels";

export async function POST() {
  const uuid = crypto.randomUUID();
  const channel = helloChannel({ uuid });

  // Subscribe first so the stream includes the function's first update.
  const stream = await inngest.realtime.subscribe({
    channel,
    topics: ["logs"],
  });

  await inngest.send({ name: "hello-world/hello", data: { uuid } });

  return new Response(stream.getEncodedStream(), {
    headers: {
      "Content-Type": "text/event-stream",
      "Cache-Control": "no-cache",
      Connection: "keep-alive",
    },
  });
}
```

`realtime.Subscribe()` needs a subscription token for the channel, and the Go SDK can't mint one per request. Mint the token with the TypeScript or Python SDK.

Authorize the caller in this route, just as you would before you mint a token. The route reads with your signing key, so it can read any channel.

The stream also offers `getJsonStream()` for a stream of parsed messages and `close()` to end the subscription.

## Next steps

- [Subscribing reference](/docs-markdown/reference/typescript/v4/realtime/subscribing) lists every option and message field.
- [Subscription tokens](/docs-markdown/realtime/guides/subscription-tokens) covers browser subscriptions.
- [Troubleshooting](/docs-markdown/realtime/troubleshooting#a-server-side-subscriber-receives-nothing) helps when a server subscriber receives nothing.