Payment completion notice

An example where your server notifies the chat room through the server REST API (v2) when a payment completes, and a page script receives it and shows it in the chat window.

Last updated October 19, 2026

On this page

The moment a payment completes on your server, show a "purchase complete" notice to everyone in the chat room. Your server sends data to the payment channel with the Server REST API, and the page script checks that it came from the server before showing it in the chat window.

Flow

  1. The payment provider tells your server the payment is complete (your server's existing handling).
  2. Your server sends to the payment channel with POST https://api.uchat.io/v2/rooms/{id}/events.
  3. The data arrives on the payment channel in the page of everyone currently in the chat room.
  4. The page script checks that the sender's permission is 4 (server) and shows it with room.print.

Server: Node.js

This uses the built-in fetch in Node.js 18 or later. Issue the API key and find the API chat room id (room_…) in the API menu of the admin page, and keep the key only in your server's environment variables. The key needs the Events permission.

// notify-payment.mjs
const ROOM = 'room_xxxxxxxxxxxxxxxx'; // API chat room id

export async function notifyPayment(productName) {
	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: 'payment', data: ['paid', productName] }),
	});
	const result = await res.json();
	if (!res.ok) {
		// 429 chat_rate_limited: free chat room send limit exceeded, etc.
		console.warn('uchat notify failed', res.status, result.error);
	}
	return result;
}

// Inside your payment completion handling
// await notifyPayment('Premium pass');

Server: PHP

<?php
function notify_payment($product_name) {
	$ch = curl_init('https://api.uchat.io/v2/rooms/room_xxxxxxxxxxxxxxxx/events');
	curl_setopt_array($ch, [
		CURLOPT_POST => true,
		CURLOPT_RETURNTRANSFER => true,
		CURLOPT_HTTPHEADER => [
			'Authorization: Bearer ' . getenv('UCHAT_API_KEY'),
			'Content-Type: application/json',
		],
		CURLOPT_POSTFIELDS => json_encode(['plugin' => 'payment', 'data' => ['paid', $product_name]], JSON_UNESCAPED_UNICODE),
		CURLOPT_TIMEOUT => 5,
	]);
	$body = curl_exec($ch);
	$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
	curl_close($ch);
	$result = json_decode($body, true);
	if ($status >= 400) {
		error_log('uchat notify failed: ' . $status . ' ' . ($result['error']['code'] ?? 'no response'));
	}
	return $result;
}

// Inside your payment completion handling
// notify_payment('Premium pass');

On success, it returns HTTP 202 and {"data":{"sent":true}}. It succeeds even when no one is in the chat room (there is just no one to receive it). On failure, you get an HTTP status and {"error":{"code":…,"message":…}}. For the full list of error codes, see Server REST API.

Page: receive and display

<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:400px;height:500px"></u-chat>

<script>
U.chat('my-room').on('after.create#payment', function (room) {
	function esc(value) {
		return String(value).replace(/[&<>"']/g, function (c) {
			return '&#' + c.charCodeAt(0) + ';';
		});
	}

	room.plugin.add('payment').onReceived(function (room, data) {
		// Trust only what the server API sent. Users can send to the same channel too
		if (!data.user || data.user.auth !== 4) return;
		if (data.type !== 0) return; // Skip saved values that arrive on join (type 1)
		var msg = data.data;
		if (msg[0] !== 'paid' || typeof msg[1] !== 'string') return;
		room.print('🎉 Someone just bought <b>' + esc(msg[1].slice(0, 50)) + '</b>!');
	});
});
</script>

Things to note

  • room.print displays HTML, so always escape received values as in the code above.
  • If you display without checking permissions, any user can show fake payment notices through the same channel. data.user.auth === 4 is set only on data sent through the server API or values saved by a moderator or higher. Saved values arrive with type 1 on join, so the code above accepts only type 0 (sent to everyone through the server API). Server API data sent to a specific nickname is also type 1, so it can't be told apart from saved values.
  • The start of the REST request body and the request IP are kept for 30 days in the call log of the API menu on the admin page. Do not send personal information such as the buyer's name, contact details, or card details.
  • Data sent without save reaches only people connected now. The API responds with success even if no one is in the chat room, and even if a call fails, don't fail the payment itself; just log it. A payment notice is about something that already happened, so it should not be saved with save.
  • To send to one person only, put "to":"nickname" in the body (Korean nicknames work too). Data sent this way arrives as type 1, so remove the type check from the code above.
  • In free chat rooms, you get HTTP 429 chat_rate_limited if the total number of recipients over the last 60 seconds exceeds the limit (Channel API and limits).
  • To just show it in the chat room without code, turn on the Event alert box plugin and send with plugin set to alertBox and data set to ["custom", "Purchase complete", product name].
  • The general flow for sending server notifications is in Sending notifications from your server to a chat room.

Still not sure how it works? Ask your question on the contact board.

Business name UCHAT · CEO 김경욱 · Business registration no. 141-05-32832
Address 경기도 파주시 와석순환로 61 · Mail-order business registration no. 2013-경기파주-4485호
Phone 010-8677-8633 · Email support@uchat.io

ⓒ 2013–2026 UCHAT. All rights reserved.