floors.js Documentation
Configure everything from your dashboard.
Customize your widget
Configure everything easily from your dashboard.
as to rename: /ugly-path as Pretty Name. Up to 10 rooms.{ uid, name, exp } signed with your Widget secret (Dashboard → Settings). Sign it on your backend only — never expose the secret to the browser. SPA alternative: call window.Floors.identify(token) after the user logs in.🔒 "Users only" — backend setup
Enable Users only in your Dashboard and copy your Widget secret. Then, on pages rendered for a logged-in user, have your backend sign a token and pass it as data-user-token (or via window.Floors.identify(token)). floors.js never touches your user database — it only verifies the signature.
Token format (all languages)
payload = base64url(JSON({ uid, name, exp: nowMs + 3600000 }))
sig = base64url( HMAC_SHA256(payload, WIDGET_SECRET) )
token = payload + "." + sig Next.js (App Router)
// app/lib/floors.ts
import crypto from "crypto";
const b64url = (b) => Buffer.from(b).toString("base64url");
export function floorsToken(user) {
const payload = b64url(JSON.stringify({
uid: user.id, name: user.name, exp: Date.now() + 3600_000,
}));
const sig = crypto.createHmac("sha256", process.env.FLOORS_WIDGET_SECRET)
.update(payload).digest("base64url");
return payload + "." + sig;
}
// app/(app)/layout.tsx — server component
import { floorsToken } from "@/app/lib/floors";
import { getSession } from "@/app/lib/auth"; // your auth
export default async function AppLayout({ children }) {
const session = await getSession();
const token = session ? floorsToken(session.user) : null;
return (
<>
{children}
<script
src="https://floorsjs.com/embed/floors.min.js"
data-key="flr_..."
{...(token ? { "data-user-token": token } : {})}
async
/>
</>
);
} Next.js (Pages Router) / React SPA
// pages/api/floors-token.ts — returns a token for the logged-in user
import crypto from "crypto";
import { getSession } from "@/lib/auth";
const b64url = (b) => Buffer.from(b).toString("base64url");
export default async function handler(req, res) {
const session = await getSession(req);
if (!session) return res.status(401).end();
const payload = b64url(JSON.stringify({
uid: session.user.id, name: session.user.name, exp: Date.now() + 3600_000,
}));
const sig = crypto.createHmac("sha256", process.env.FLOORS_WIDGET_SECRET)
.update(payload).digest("base64url");
res.json({ token: payload + "." + sig });
}
// client, after login:
const { token } = await fetch("/api/floors-token").then(r => r.json());
window.Floors?.identify(token); Node.js / Express
import crypto from "crypto";
const b64url = (b) => Buffer.from(b).toString("base64url");
const SECRET = process.env.FLOORS_WIDGET_SECRET;
app.get("/app", (req, res) => {
let tag = '<script src="https://floorsjs.com/embed/floors.min.js" data-key="flr_..." async></scr'+'ipt>';
if (req.user) {
const payload = b64url(JSON.stringify({
uid: req.user.id, name: req.user.name, exp: Date.now() + 3600_000,
}));
const sig = crypto.createHmac("sha256", SECRET).update(payload).digest("base64url");
const token = payload + "." + sig;
tag = '<script src="https://floorsjs.com/embed/floors.min.js" data-key="flr_..." data-user-token="' + token + '" async></scr'+'ipt>';
}
res.send(renderPage({ floorsTag: tag }));
}); PHP / Laravel
function floors_token($user) {
$secret = env('FLOORS_WIDGET_SECRET');
$payload = rtrim(strtr(base64_encode(json_encode([
'uid' => $user->id, 'name' => $user->name,
'exp' => (int)(microtime(true) * 1000) + 3600000,
])), '+/', '-_'), '=');
$sig = rtrim(strtr(base64_encode(
hash_hmac('sha256', $payload, $secret, true)
), '+/', '-_'), '=');
return $payload . '.' . $sig;
}
// Blade (logged-in only):
// @auth <script src="https://floorsjs.com/embed/floors.min.js"
// data-key="flr_..." data-user-token="{{ floors_token(auth()->user()) }}"></script> @endauth Python / Django / Flask
import os, json, time, hmac, hashlib, base64
def b64url(b: bytes) -> str:
return base64.urlsafe_b64encode(b).rstrip(b"=").decode()
def floors_token(user) -> str:
secret = os.environ["FLOORS_WIDGET_SECRET"].encode()
payload = b64url(json.dumps({
"uid": str(user.id), "name": user.name,
"exp": int(time.time() * 1000) + 3_600_000,
}).encode())
sig = b64url(hmac.new(secret, payload.encode(), hashlib.sha256).digest())
return f"{payload}.{sig}"
# template context: token = floors_token(request.user) if request.user.is_authenticated else None Ruby on Rails
require "openssl"; require "base64"; require "json"
def floors_token(user)
secret = ENV["FLOORS_WIDGET_SECRET"]
payload = Base64.urlsafe_encode64(
{ uid: user.id, name: user.name, exp: (Time.now.to_f * 1000).to_i + 3_600_000 }.to_json,
padding: false
)
sig = Base64.urlsafe_encode64(
OpenSSL::HMAC.digest("SHA256", secret, payload), padding: false
)
"#{payload}.#{sig}"
end
# erb (logged-in only):
# <% if user_signed_in? %><script src="https://floorsjs.com/embed/floors.min.js"
# data-key="flr_..." data-user-token="<%= floors_token(current_user) %>"></script><% end %> exp short (≈1h); refresh by re-rendering the page or calling window.Floors.identify(newToken). Rotating the Widget secret invalidates all existing tokens.
Discord notifications
Get pinged on Discord when a visitor sends a message on your site.
After your payment, you'll receive an email with a private link to your Dashboard.
Click the link, go to Settings, paste your Discord webhook URL, and hit Save. Done.
Telegram bot
Reply to your visitors directly from Telegram. All chat messages are forwarded to your Telegram chat, and your replies appear in the widget in real time.
Create a bot with @BotFather on Telegram and copy the bot token.
Open your Dashboard, go to Settings, paste the bot token and chat ID, then hit Connect.
Reply to any forwarded message in Telegram — your answer appears instantly in the widget chat.
Content-Security-Policy error, your site blocks external scripts. Add floorsjs.com to your CSP:
script-src ... https://floorsjs.comconnect-src ... https://floorsjs.com wss://server.floorsjs.com