---
title: Realtime quickstart
description: Push a message to a connected client in five calls with one key - create a channel, mint a token, connect, publish, and resume from a cursor.
type: tutorial
product: realtime
summary: A channel, a connected client, a published message, and a resume after a drop
updated: 2026-10-07
order: 1
---

This takes you from nothing to a message pushed to a connected client, using
only a key and `curl`. Nothing is deployed: the channel lives in Sylphx Events.

<Prerequisites
	items={[
		'An account, and a project with an environment',
		'A secret key holding the events:write and events:publish scopes, as SYLPHX_API_KEY',
		'curl and jq',
	]}
/>

The examples use the environment `orgs/acme/projects/shop/envs/production`;
use your own. `sylphx access whoami` prints the environment your key belongs
to, and a key only reaches its own environment.

```bash
export ENV=orgs/acme/projects/shop/envs/production
```

## 1. Create the channel

The channel id is the last segment of its name, and your clients and servers
will name it, so choose it rather than letting the server assign one.

**cURL**

```bash
curl -sS -X POST "https://api.sylphx.com/v1/$ENV/realtime_channels?realtime_channel_id=orders" \
  -H "Authorization: Bearer $SYLPHX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"spec": {"history_retention": "86400s"}}'
```

**TypeScript**

```ts
import { Sylphx } from '@sylphx/sdk'

const sylphx = new Sylphx()
const { env } = await sylphx.access.whoami({})
const channel = await sylphx.events.realtimeChannels.create({
  parent: env,
  realtimeChannelId: 'orders',
  realtimeChannel: { spec: {} },
})
```

The answer carries `status.connect_uri` (WebSocket) and `status.stream_uri`
(Server-Sent Events): the two addresses clients connect to.

## 2. Mint a token for the client

A browser or device never holds your secret key. Your server mints a token for
this one channel and hands it to the client; it lives at most an hour.

```bash
curl -sS -X POST "https://api.sylphx.com/v1/$ENV/realtime_channels/orders:issueToken" \
  -H "Authorization: Bearer $SYLPHX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"ttl": "3600s", "client_id": "user-42"}' > token.json
export STREAM_URI=$(jq -r '.connect_uri | sub("^wss://"; "https://") | sub("/subscribe$"; "/stream")' token.json)
export RT_TOKEN=$(jq -r .token token.json)
```

The answer has the `token` (`rtt_…`, returned once), its `expire_time` and the
`connect_uri`. Naming a `client_id` puts the client in the channel's presence
while it is connected.

## 3. Connect a client

In a second terminal, read the channel's Server-Sent Events stream with the
token. It stays open and prints each message as it arrives.

```bash
curl -sS -N "$STREAM_URI?token=$RT_TOKEN"
```

In a browser, the WebSocket at `connect_uri` does the same and carries presence:

```ts
const socket = new WebSocket(`${connectUri}?token=${token}`)
socket.onmessage = (frame) => {
  const { cursor, event } = JSON.parse(frame.data)
  // event.type is the message type, event.data the payload,
  // event.subject the publisher's client id.
  localStorage.setItem('orders-cursor', cursor)
}
```

## 4. Publish

Back in the first terminal, publish a message. Every connected client receives
it at once.

**cURL**

```bash
curl -sS -X POST "https://api.sylphx.com/v1/$ENV/realtime_channels/orders:publish" \
  -H "Authorization: Bearer $SYLPHX_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1001-shipped" \
  -d '{"message_type": "order.shipped", "data": {"order": "1001"}}'
```

**TypeScript**

```ts
await sylphx.events.realtimeChannels.publish({
  name: `${env}/realtime_channels/orders`,
  messageType: 'order.shipped',
  data: { order: '1001' },
})
```

The stream prints one frame. Its `event:` line is the message type, its `id:`
line the cursor, and `data:` the message as JSON: `cursor`, and an `event` whose
`type`, `data` and `subject` are the message type, the payload and the
publisher's client id. The same `Idempotency-Key` sent again returns this
message and appends nothing.

## 5. Resume after a drop

Stop the stream, publish twice more, then reconnect from the cursor the stream
last printed:

```bash
curl -sS -N "$STREAM_URI?token=$RT_TOKEN&after_cursor=<cursor>"
```

The two messages published while you were away arrive first, in order, and
then the stream carries on live. An SSE client sends `Last-Event-ID` on its
own; a WebSocket client sends `{"op": "subscribe", "afterCursor": "<cursor>"}`.

## Read it back

```bash
curl -sS "https://api.sylphx.com/v1/$ENV/realtime_channels/orders/realtime_messages" \
  -H "Authorization: Bearer $SYLPHX_API_KEY"
```

The channel's history, in order, for as long as `history_retention` keeps it.
`realtime_members` lists who is connected now, and the channel's
`status.connection_count` counts them.

## Next

<RelatedDocs
	links={[
		{
			href: '/docs/realtime',
			label: 'Realtime',
			description: 'Channels, history, tokens and presence.',
		},
		{
			href: '/docs/api/realtime_channels',
			label: 'The realtime_channels collection',
			description: 'Every method, its scope and its examples.',
		},
		{
			href: '/docs/platform/keys-and-scopes',
			label: 'Keys and scopes',
			description: 'Secret and publishable keys, and what each may do.',
		},
	]}
/>
