---
sidebar_position: 5
---

# Sending from your server

Your server sends events to rooms and to users with its secret, in the same chain in every package. Pages send to rooms too, as a room allows ([events from the page](events.md)). Only your server sends to a user.

## To rooms and users

**Node**

```ts
await realtime.to( 'order:42' ).emit( 'updated', { status: 'paid' } );
await realtime.to( [ 'order:42', 'staff' ] ).except( tab ).emit( 'updated', { id: 42 } );   // not back to the tab that made it
await realtime.toUser( 42 ).emit( 'notice', { text: 'Your order shipped' } );
```

**PHP**

```php
$realtime->to( 'order:42' )->emit( 'updated', [ 'status' => 'paid' ] );
$realtime->to( [ 'order:42', 'staff' ] )->except( $tab )->emit( 'updated', [ 'id' => 42 ] );
$realtime->toUser( 42 )->emit( 'notice', [ 'text' => 'Your order shipped' ] );

// Laravel: an event that implements ShouldBroadcast, on PrivateChannel( 'orders.42' ), the room orders.42
broadcast( new OrderPaid( $order ) )->toOthers();
```

**Python**

```python
realtime.to("order:42").emit("updated", {"status": "paid"})
realtime.to(["order:42", "staff"]).except_tab(tab).emit("updated", {"id": 42})
realtime.to_user(42).emit("notice", {"text": "Your order shipped"})
await realtime.to("order:42").aemit("updated", {"status": "paid"})   # from async code: FastAPI, Starlette, async Django views
```

| | |
|---|---|
| `to( rooms )` | one room, or up to 1,000 |
| `toUser( users )` (Python `to_user`) | one user, or up to 1,000: every tab and device they have open, through a room of their own WordPlus Cloud keeps. It chains with `to()` |
| `except( tab )` (Python `except_tab`) | a tab (the browser `rt.tab`, sent with the request that caused it, as `X-Realtime-Tab`), which does not get it back. The Laravel `toOthers()` takes it from that header |
| `emit( event, data, { seal } )` | any name but those kept for the room itself (`joined`, `join`, `leave`, `count`, `error`). Data anything JSON takes, binary too, up to 1 MB. `seal: false` sends the data of a sealed room plain |

- **Sealed by default:** the data of a sealed room goes sealed with its key, a copy each, and what goes to a user with their own key. The data of public rooms goes plain, together.
- **Kept:** a room whose rule keeps history keeps it, and the answer is the kept event ids, by room.
- **A refusal throws** a `RealtimeError` with the server `code` and HTTP `status` ([errors](errors.md#sending)). The Laravel broadcasting throws a `BroadcastException`. Send after your own work is saved: a failed send is a missed live update, not a lost change.
- **A user with nothing open** gets nothing: `toUser()` reaches the connections they have, and push stays out of this SDK for now.

## Many at once

**Node**

```ts
await realtime.batch( [
    { rooms: 'order:42', event: 'updated', data: { status: 'paid' } },
    { users: 42, event: 'notice', data: { text: 'Paid' } },
] );   // 100 events a request
```

**PHP**

```php
$realtime->batch( [
    [ 'rooms' => 'order:42', 'event' => 'updated', 'data' => [ 'status' => 'paid' ] ],
    [ 'users' => 42, 'event' => 'notice', 'data' => [ 'text' => 'Paid' ] ],
] );
```

**Python**

```python
realtime.batch([
    {"rooms": "order:42", "event": "updated", "data": {"status": "paid"}},
    {"users": 42, "event": "notice", "data": {"text": "Paid"}},
])
```

## Asking about a room

**Node**

```ts
await realtime.in( 'room:7' ).count();
await realtime.in( 'room:7' ).members();                      // { count, list: [ { id, info } ], next? }, their info opened
await realtime.in( 'chat:34:42' ).history( { limit: 50 } );   // { events, more }, oldest first, opened
await realtime.in( 'chat:34:42' ).forget( id );               // one kept event, forget() for all
await realtime.in( 'chat:34:42' ).kick( 43 );                 // out now
```

**PHP**

```php
$realtime->in( 'room:7' )->members();
$realtime->in( 'chat:34:42' )->kick( 43 );
```

**Python**

```python
realtime.in_room("room:7").members()
realtime.in_room("chat:34:42").kick(43)
```

**Kick** takes the user connections out of the room at once, and refuses them until their token expires. With the key generations kept ([encryption](encryption.md#when-someone-loses-access)), it also moves the room key on, so a sealed event after the kick is not theirs to read.

## Webhooks

WordPlus Cloud tells your server what happens in rooms, at the address set on your app page in your WordPlus Cloud account (`https://shop.example.com/api/realtime/webhook`), which the package routes answer:

**Node**

```ts
realtime.on( 'message', async ( { room, from, data, event_id, at } ) => saveMessage( { id: event_id, room, from, text: data.text, at } ) );
realtime.on( 'user.offline', ( { user } ) => markAway( user ) );
```

**PHP**

```php
$realtime->on( 'message', fn ( array $e ) => save_message( $e['event_id'], $e['room'], $e['from'], $e['data']['text'], $e['at'] ) );
```

**Python**

```python
realtime.on("message", lambda e: save_message(e["event_id"], e["room"], e["from"], e["data"]["text"], e["at"]))
```

| Event | |
|---|---|
| a page event name | `{ id, at, room, event, data, from, event_id }`, for the events a room rule names in `webhook`. `event_id` is the id the pages saw (`meta.id`), what your app keeps it by |
| `room.occupied`, `room.vacated` | `{ id, at, room }`: the first one in, the last one out |
| `member.joined`, `member.left` | `{ id, at, room, user }`, in a presence room |
| `user.online`, `user.offline` | `{ id, at, user }` |
| `*` | every one of them |

- **Signed** with a key of its own (`wordplus-realtime webhook v1`), and checked before a handler sees it. Sealed data comes opened.
- **Batched,** and sent **at least once:** each event has its id, so a repeat can be dropped. A request that fails is tried again, later each time, for 24 hours.
