# Sending notifications from your server to a chat room

> How to send data from your server to a chat room channel with the server API (v2), and how your page script or a plugin can pick out and show only the data your server sent.

When something happens on your server (a payment completes, a new post goes up, a stream starts), you can send it to a chat room channel with the server API, and your page script can receive it and show it in the chat room. The full request format and error codes are in [Server REST API](/en/doc/3.6).

To just show it without writing code, turn on the **Event alert box** plugin and send with `plugin` set to `alertBox`. It shows new post, donation, and subscription alerts as colored boxes in the chat list. The Donation goal bar, Live status, Scoreboard, Countdown, and Ranking plugins work the same way.

## Flow {#flow}

1. Your page script (or a plugin) opens a channel (for example, `notice-bot`) in the chat room and waits.
2. Your server sends `{"plugin":"notice-bot","data":[...]}` to `POST https://api.uchat.io/v2/rooms/{id}/events`.
3. People currently connected to the chat room receive the data on that channel. The sender is the server user, with `auth` 4.
4. Your page script checks `auth` and shows the data on the screen.

By default, it goes only to people connected right now. If no one is there, there is simply no one to receive it; it is not an error. If people who join later should also see it, save it with `"save": true` (see [For people who join later](#save) below).

## API key {#api-key}

In the admin page, go to the **API** menu and click **Create API key**. Give it a name and turn on **Events** in its permissions. The key (`uchat_…`) is shown only once when issued, so copy it right away. On the same screen, also copy the **API chat room id** (`room_…`) that goes in the URL.

Keep the API key only on your server. If you call the server API from browser JavaScript, the key is exposed and anyone can send data as the server user. If the key leaks, revoke it or roll it right away in the **API** menu.

## 1. Prepare the receiving side {#receive}

```html
<script>U=window.U=window.U||{},U.events=U.events||[],U.chat=function(n){return{on:function(e,t){U.events.push([n,e,t])},off:function(e){for(var t=U.events.length;t>0;)U.events[--t][0]==n&&U.events[t][1]==e&&U.events.splice(t,1)}}};</script>
<script async src="https://client.uchat.io/uchat.js"></script>
<u-chat room="my-room" style="display:inline-block; width:100%; max-width:500px; height:500px;"></u-chat>
<script>
function escapeHtml(text) {
	return String(text).replace(/[&<>"']/g, function (c) {
		return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
	});
}

U.chat('my-room').on('after.create', function (room) {
	room.plugin.add('notice-bot').onReceived(function (room, msg) {
		if (!msg.user || msg.user.auth !== 4) return; // only server API data or saved values
		if (msg.type !== 0) return; // only data the server API sent to everyone
		if (typeof msg.data[0] !== 'string') return;
		room.print('[Notice] ' + escapeHtml(msg.data[0]));
	});
});
</script>
```

- People in the chat room can also send data with the same channel name. To trust only what your server sent, check `msg.user.auth === 4`. `auth` 4 means data sent through the server API, or a value saved by a moderator or above. Saved values arrive with `type` 1 on entry, so to accept only server API data sent to everyone (`type` 0), also check `msg.type === 0`. Server API data sent to a specific nickname is also `type` 1, so you cannot tell it apart from saved values. The server user has no nickname, so do not check by nickname.
- `room.print` interprets its input as HTML, so escape any text you receive before inserting it.

## 2. Send from your server {#send}

curl:

```sh
curl -X POST https://api.uchat.io/v2/rooms/room_xxxxxxxxxxxxxxxx/events \
  -H "Authorization: Bearer uchat_..." \
  -H "Content-Type: application/json" \
  -d '{"plugin":"notice-bot","data":["Payment for order #1024 is complete",12000]}'
```

- `data` is an array of values. Use strings, numbers, and booleans; the receiving side gets them all as strings.
- On success you get HTTP 202 and `{"data":{"sent":true}}`. On failure you get an HTTP status and `{"error":{"code":…,"message":…}}`.
- Before sending for real, you can run the same body with **Try it** in the **API** menu.

Node.js (18 or later):

```js
const ROOM = 'room_xxxxxxxxxxxxxxxx';

async function sendToRoom(plugin, data, options = {}) {
	const res = await fetch(`https://api.uchat.io/v2/rooms/${ROOM}/events`, {
		method: 'POST',
		headers: {
			Authorization: `Bearer ${process.env.UCHAT_API_KEY}`,
			'Content-Type': 'application/json'
		},
		body: JSON.stringify({ plugin, data, ...options })
	});
	const result = await res.json();
	if (!res.ok) console.warn('uchat api error', res.status, result.error);
	return result;
}

await sendToRoom('notice-bot', ['Payment for order #1024 is complete', 12000]);
```

PHP:

```php
<?php
function uchat_send($plugin, array $data, array $options = []) {
	$ch = curl_init('https://api.uchat.io/v2/rooms/room_xxxxxxxxxxxxxxxx/events');
	curl_setopt_array($ch, [
		CURLOPT_POST => true,
		CURLOPT_HTTPHEADER => [
			'Authorization: Bearer ' . getenv('UCHAT_API_KEY'),
			'Content-Type: application/json',
		],
		CURLOPT_POSTFIELDS => json_encode(['plugin' => $plugin, 'data' => $data] + $options),
		CURLOPT_RETURNTRANSFER => true,
	]);
	$result = json_decode(curl_exec($ch), true);
	curl_close($ch);
	return $result;
}

uchat_send('notice-bot', ['Payment for order #1024 is complete', 12000]);
```

## Send to one person {#send-to-one}

Put a nickname in `"to"` in the body to send only to that person. On the receiving side, `msg.type` is 1. Non-English nicknames such as Korean ones work as is.

```sh
curl -X POST https://api.uchat.io/v2/rooms/room_xxxxxxxxxxxxxxxx/events \
  -H "Authorization: Bearer uchat_..." \
  -H "Content-Type: application/json" \
  -d '{"plugin":"notice-bot","to":"Minji","data":["Minji, your coupon has been issued"]}'
```

If that nickname is not in the chat room right now, you get HTTP 404 `target_not_found`.

## For people who join later {#save}

Add `"save": true` to save it as the channel's last value. It is sent to people here now, and people who join later receive it on entry (`msg.type` 1). It is saved even if you send it when no one is in the chat room.

```sh
curl -X POST https://api.uchat.io/v2/rooms/room_xxxxxxxxxxxxxxxx/events \
  -H "Authorization: Bearer uchat_..." \
  -H "Content-Type: application/json" \
  -d '{"plugin":"notice-bot","save":true,"data":["Maintenance tonight at midnight"]}'
```

- To delete it, save `"data":[]`. The receiving side gets an empty value.
- The limits are the same as channel save (a free chat room can save one channel, up to 1KB). → [Channel API and limits](/en/doc/3.5#save)
- Saved values remain even when the chat room empties or the server restarts.
- The receiving example above drops `msg.type !== 0`, so remove that line if you also want saved values.

## Common errors {#errors}

| Error | Meaning | What to do |
| --- | --- | --- |
| 401 `missing_key`, `invalid_key` | No key, or not a key for this chat room | Check the `Authorization: Bearer` header and the API chat room id. Use the API key, not the member integration token |
| 403 `insufficient_scope` | The key lacks the Events permission | Fix that key's permissions in the **API** menu |
| 400 `invalid_data` | `data` is not an array, or contains control characters such as line breaks | Send it in the form `[...]`, without line breaks |
| 404 `target_not_found` | The `to` nickname is not in the chat room | Check the nickname |
| 429 `rate_limited`, `chat_rate_limited` | Call or channel send limit exceeded | Send less often. → [Server REST API](/en/doc/3.6#response) |

The start of the request body and the request IP are kept for 30 days in the call log of the **API** menu. Do not send personal or payment information; send only the text to display. A complete example is in [Payment completion notice](/en/doc/4.4). The older v1 method (`/rooms/{room}/plugin/...`) is covered in [Server REST API](/en/doc/3.6#v1).
