Add the changelog to your app Quick Start Drop UserRelay into any web page with a short script tag. Works on static HTML, single-page apps, and server-rendered sites alike.
Step 1

Grab your project key

From the dashboard, head to Settings › Project. Copy your Project key — you'll paste it into your page in a moment.

Step 2

Drop in the script

Inside the <head> (or anywhere before </body>) of any page where you want the widget available:

<script>
    var user_relay_config = {
        project_key: "your-project-key",
    };
</script>
<script src="https://userrelay.com/embed/script.js" defer></script>

That's it. Reload your page — you'll see a floating button in the bottom-right corner. Click it and the widget panel slides in, showing all of your published posts.

Step 3

Lock the widget to your site (recommended)

By default the widget will render anywhere the script tag is included. To stop someone else from pulling your project key and embedding your changelog on their own site, lock it down to a list of domains.

In Settings › Project › Security, pick Allowed domains and add your site (e.g. example.com). UserRelay then checks each request's Referer header — requests from anywhere else get a 404 and the widget silently disappears.

Step 4

Identify your users (optional)

If your site has logged-in users, pass their email and name. UserRelay then tracks which posts each user has seen, which segments they belong to, and whether they're subscribed to email updates.

Add a user block to your config:

<script>
    var user_relay_config = {
        project_key: "your-project-key",
        user: {
            name:  "Jane Doe",
            email: "jane@example.com",
        },
    };
</script>

As long as the request is coming from one of your allowed domains, UserRelay trusts the email at face value. If you skipped step 3 (allowed domains), the widget will render but anonymous visitors can pass any email they like in their browser's devtools — fine for low-stakes data; not fine if you care about who's subscribed to what.

Step 5

Customize (optional)

Any of these can go inside user_relay_config:

var user_relay_config = {
    project_key: "your-project-key",

    // Filter which posts this visitor sees, by segment name.
    // Use "*" for every segment.
    segments: "engineering,beta",

    // Filter by category name.
    categories: "NEW,FIX",

    // true, false, or "system" to follow the visitor's OS setting.
    dark: "system",

    // Hide the floating button. Open the widget yourself by adding
    // the attribute  ur-opener  to any element on the page.
    disable_btn: true,

    user: {
        name:     "Jane Doe",
        email:    "jane@example.com",
        // Per-user segment membership. "*" puts them in every segment.
        segments: "engineering,beta",
    },
};

You can also drive the widget programmatically by posting messages from your own code:

// Open it
parent.postMessage(['open-ur-widget'], '*');

// Close it
parent.postMessage(['close-ur-widget'], '*');
Advanced

Signed identity (highest security)

Allowed domains stops random sites from embedding your widget, but a determined visitor on your own site can still open devtools and swap out the email, segments, filters — anything you set client-side. For most cases that's fine. If it's not, switch Settings › Project › Security to Signed identity.

In this mode UserRelay refuses to render the widget at all unless the request carries a valid HMAC signature — and the signature covers the entire payload of user info and filters, so any tampering breaks it.

On your server, build the same object you'd otherwise put in user_relay_config (minus the project key). Base64-encode the JSON, then append a dot and the HMAC of that base64 string — signed with your Widget secret. The result is one self-contained token:

// PHP
$encoded = base64_encode(json_encode([
    'user' => [
        'name'     => $user->name,
        'email'    => $user->email,
        'segments' => 'engineering,beta',
    ],
    'segments'   => 'engineering,beta',
    'categories' => 'NEW,FIX',
]));
$payload = $encoded . '.' . hash_hmac('sha256', $encoded, env('USER_RELAY_SECRET'));
// Node.js
const crypto = require('crypto');
const encoded = Buffer.from(JSON.stringify({
    user: {
        name:     user.name,
        email:    user.email,
        segments: 'engineering,beta',
    },
    segments:   'engineering,beta',
    categories: 'NEW,FIX',
})).toString('base64');
const payload = encoded + '.' + crypto
    .createHmac('sha256', process.env.USER_RELAY_SECRET)
    .update(encoded)
    .digest('hex');
# Ruby
require 'base64'
require 'json'
require 'openssl'

encoded = Base64.strict_encode64({
    user: {
        name:     user.name,
        email:    user.email,
        segments: 'engineering,beta',
    },
    segments:   'engineering,beta',
    categories: 'NEW,FIX',
}.to_json)
payload = "#{encoded}.#{OpenSSL::HMAC.hexdigest('SHA256', ENV['USER_RELAY_SECRET'], encoded)}"
# Python
import base64, hmac, hashlib, json, os

encoded = base64.b64encode(json.dumps({
    'user': {
        'name':     user.name,
        'email':    user.email,
        'segments': 'engineering,beta',
    },
    'segments':   'engineering,beta',
    'categories': 'NEW,FIX',
}).encode()).decode()
signature = hmac.new(
    os.environ['USER_RELAY_SECRET'].encode(),
    encoded.encode(),
    hashlib.sha256,
).hexdigest()
payload = f"{encoded}.{signature}"

Then ship just the token to the browser. The signed payload replaces every config field client-side — nothing in it can be tampered with without breaking the signature:

var user_relay_config = {
    project_key: "your-project-key",
    payload:     "<the signed token you computed>",
};

Anonymous visitors (anyone without a valid payload) will not see the widget at all. If you want both anonymous and signed visitors, stay on Allowed domains mode instead.

Stuck somewhere? We'll help. Contact Us
Terms of Service | Privacy Policy © 2026 UserRelay. All rights reserved.