Landmark DocsDashboard →

Reference

window.landmark

Your element's HTML/CSS/JS runs inside a sandboxed iframe (sandbox="allow-scripts" only — no parent access, no cookies). The window.landmark object is your bridge to the stream: the event that fired it, the live event feed, your tunable config, persistent storage, and text-to-speech. Standard browser APIs (DOM, Canvas, requestAnimationFrame, fetch to same-origin, timers) all work. localStorage does not — use landmark.vars instead.

At a glance

window.landmark = {
  event:    { platform, type, usd_amount, payload, occurred_at },  // trigger
  mode:     "alert" | "persistent",   // one-shot pop-up vs long-lived widget
  preview:  boolean,                  // true in the editor preview (no billing)
  config:   { …knobs },               // your _schema controls, keyed by id
  events:   [ …recent ],              // live feed, newest first, capped at 200
  on(type, cb)        => unsubscribe, // subscribe to the live feed
  vars: { get(key) => Promise, set(key, value) => Promise },  // JSONB storage
  tts(text, opts?),                   // speak text on the live overlay
  dismiss()                           // close an alert early
}

Properties

landmark.event
The trigger that mounted this element (read-only). Shape: { platform, type, usd_amount, payload, occurred_at }. For a persistent element this is a placeholder ({ platform: "", type: "", payload: {} }) until events arrive — rely on landmark.on() instead. See event payloads for the fields inside payload.
landmark.mode  "alert" | "persistent"
"alert" — a one-shot pop-up that auto-dismisses after its duration_ms. "persistent" — a long-lived widget (chat box, leaderboard, goal bar, always-on prize wheel) that mounts once and reacts to the live feed. Branch on it if your element supports both.
landmark.preview  boolean
true in the editor / hub preview, false on the live OBS overlay. The preview silences audio (so a grid of previews doesn't blast sound) and makes landmark.tts() a no-op (so previews never bill). You rarely need to read this directly — the runtime already gates sound and TTS for you.
landmark.config  Record<string, unknown>
Values from your _schema knobs, keyed by spec id, so streamers can tune your element without editing code. Each schema entry is { id, label, type, default, min?, max?, step?, options? } where type is one of color, number, slider, text, select, toggle. Always default-guard: const c = landmark.config || ;
landmark.events  Array<Event>
Recent live events, newest first, capped at 200. Handy to backfill a chat box or leaderboard on mount before you start listening with on(). Each entry has the same shape as the callback in landmark.on().

Methods

landmark.on(type, cb)() => void
Subscribe to the live event feed. type is an event type string ("chat", "follow", "subscribe", "cheer", "superchat", "gift", "raid", "redemption", "like", "share", "viewers") or "*" for all. The callback gets one event object { platform, type, usd_amount, payload, occurred_at }. Returns an unsubscribe function. This is what makes a chat box, live leaderboard, or always-on wheel possible.
const off = landmark.on("cheer", (e) => {
  spawnConfetti(e.payload.amount);     // bits
  if ((e.usd_amount || 0) >= 5) landmark.tts(e.payload.user + " cheered big!");
});
// later: off();  // stop listening
landmark.vars.get(key)Promise<value | null>
landmark.vars.set(key, value)Promise<true>
Async persistent JSONB storage scoped to this element — counters, leaderboards, anything that survives between fires. Values must be JSON-serializable. (localStorage is unavailable in the sandbox; use this instead.) On the live overlay these read/write the token-authed variables store; in the editor preview they're held in memory (and can be seeded so a preview shows real-looking numbers).
const n = ((await landmark.vars.get("count")) || 0) + 1;
await landmark.vars.set("count", n);
landmark.tts(text, opts?)
Read text aloud through your TTS. opts.voice (string) is optional. On the live overlay it enqueues a read on your account, synthesized and billed through your normal TTS pipeline — an AI voice is Landmark Usage, a standard voice is free. In the editor preview it's a no-op, so previews never bill. text is truncated to 600 characters; empty/whitespace strings are ignored. Use it for moments worth speaking (goal reached, a big tip) — never per chat line.
landmark.tts("We just hit the goal!", { voice: "Brian" });
landmark.dismiss()
Close an alert early (before its duration_ms). No-op for persistent elements.

Live events

Every callback from landmark.on() (and every entry in landmark.events) is one normalized event:

{
  platform:    "twitch" | "google" | "kick" | "rumble" | "tiktok" | "portal" | "game:<slug>",
  type:        "chat" | "follow" | "subscribe" | "cheer" | "superchat" |
               "gift" | "raid" | "redemption" | "like" | "share" | "viewers",
  usd_amount:  number | null,   // normalized USD for money events; null otherwise
  occurred_at: "2026-06-25T12:00:00Z",
  payload:     { /* type-specific — see below */ }
}

platform is "google" for YouTube. Always guard for missing fields — payload shapes vary by platform (a Twitch cheer has bits; a TikTok gift has diamonds). Fields marked optional below are only present on some platforms.

chat

payload.user string
Display name of the chatter.
payload.text string
The message text (emote shortcodes stripped to readable names where applicable).
payload.user_id · user_login string?
Platform user id / login when available (Twitch sends both; YouTube/Kick send user_id; TikTok sends userId + uniqueId).
payload.badges Array<{ type, img? }>?
Canonical role badges. typebroadcaster, owner, mod, lead_mod, vip, sub, member. img is the real badge-image URL when the platform supplied one. Omitted when there are no badges.
payload.fragments Array<{ type, text, id? }>?
Present only when the message contains emotes. type is "text" or "emote"; emote fragments carry an id for rendering the CDN image. When absent, just use text.
payload.is_mod · is_owner boolean?
YouTube convenience flags (derived from badges on other platforms).
landmark.on("chat", (e) => {
  const p = e.payload;
  addLine(p.user, p.text, p.badges || []);   // platform = e.platform
});

follow

payload.user string · payload.user_id string?
Who followed. No usd_amount (follows are engagement, not revenue).

subscribe

payload.user string
Who subscribed / became a member.
payload.tier string?
Twitch tier ("1000"/"2000"/"3000") or YouTube member-level name.
payload.member boolean
true = YouTube channel membership; false = a paid Twitch/Kick/Rumble sub. The canonical matcher uses this to tell the two apart.
payload.months · payload.streak · payload.milestone number?
Cumulative months (Twitch resub / YouTube milestone), optional streak, and YouTube milestone month. Present on resub / milestone announcements.
payload.text string?
The user-typed resub / milestone message, when shared.
usd_amount number
Normalized streamer income (Twitch T1 ≈ $2.50, T2 $5.00, T3 $12.50; YouTube member / Kick / Rumble $4.99).

cheer (Twitch bits)

payload.user string · payload.amount number
Who cheered and how many bits.
payload.text string?
The cheer message.
payload.anonymous boolean
true for an anonymous cheer.
payload.bit_image · payload.bit_tier string? / number?
Animated cheermote artwork URL + tier min-bits, when resolved.
usd_amount number
bits × $0.01.

superchat (YouTube super chats / stickers, Rumble rants)

payload.user string
Who paid.
payload.amount string
The display amount, e.g. "$5.00", "CA$10.00", "₹1,000.00". (A formatted string, not a number — usd_amount is the numeric value.)
payload.text string?
The super chat / rant message (absent on super stickers).
payload.currency · payload.tier string? / number?
ISO currency code + YouTube super-chat tier, on the Data-API path.
payload.sticker true?
Present when it's a super sticker rather than a super chat.
usd_amount number
Converted to USD from the local amount via live FX rates.

gift (gift-subs, sub bombs, TikTok / Kick item gifts)

payload.user string
The gifter ("Anonymous" for anonymous Twitch bombs).
payload.kind "subscription" | "item"
"subscription" = gifted subs / membership gifts; "item" = a TikTok/Kick virtual gift.
payload.total number?
How many subs were gifted in a community bomb.
payload.community boolean?
true for a community gift bomb (one gifter → many recipients).
payload.tier · payload.anonymous string? / boolean?
Sub tier, and whether the bomb was anonymous (Twitch).
payload.giftName · payload.diamonds · payload.totalDiamonds · payload.repeat (TikTok)
Gift name, diamonds per gift, combo total diamonds, and final combo count (e.g. ×50 roses fire one event).
payload.gift { id, name, iconUrl, cost }?
Resolved gift artwork/cost for TikTok/Kick item gifts, when known.
usd_amount number
Gift value in USD (per-sub rate × count, or diamonds × $0.005 for TikTok). 0 for a received-gift redemption.

raid (Twitch / Rumble)

payload.user string · payload.amount number
The raiding channel and the viewer count it brought. No usd_amount.

redemption (channel points / Portal redeems)

payload.user string
Who redeemed.
payload.reward string
The reward title / label.
payload.input · payload.text string | null
The viewer's typed input, when the reward requires one.
payload.reward_id · payload.slug · payload.surface string?
Stable reward id (Twitch/Kick), and the redeem slug + surface (Portal / game redeems) so you can target a specific redeem across renames.
payload.cost · payload.status number? / string?
Channel-point cost and redemption status (Twitch).

like · share · viewers

like (TikTok)
payload.user, payload.likeCount, payload.totalLikes.
share (TikTok)
payload.user (+ uniqueId, userId).
viewers (all platforms, sampled)
payload.viewerCount — periodic viewer-count heartbeat. Treat as approximate; not all platforms emit on every change.

Built-in sounds

These ship with Landmark and are free to reference with new Audio() (same-origin):

  • /sounds/elements/confetti.mp3 — celebratory pop
  • /sounds/elements/wheel-spin.mp3 · /sounds/elements/wheel-win.mp3 — spin + win
  • /sounds/elements/goal-reached.mp3 — goal chime
  • /sounds/elements/counter.mp3 — short blip

Audio plays on the live overlay (and is silenced in previews). In some browsers autoplay unlocks after the overlay's first gesture — fine for event-driven plays. Expose a sound on/off toggle in _schema so streamers can mute.

Worked example — a persistent chat box

Backfills from landmark.events on mount, then keeps up via landmark.on("chat"), honoring a _schema "max messages" knob:

const box = document.getElementById("box");
const MAX = (landmark.config && landmark.config.max) || 30;

function render(e) {
  const p = e.payload || {};
  const row = document.createElement("div");
  row.textContent = (p.user || "?") + ": " + (p.text || "");
  box.appendChild(row);
  while (box.children.length > MAX) box.removeChild(box.firstChild);
  box.scrollTop = box.scrollHeight;
}

// Backfill the last few lines (events is newest-first → reverse to oldest-first).
landmark.events.filter(e => e.type === "chat").slice(0, MAX).reverse().forEach(render);
// Then stay live.
landmark.on("chat", render);

← Back to Elements · TTS reference →

Last updated · June 25, 2026