Skip to content

Activity Alerts

Activity Alerts show live stream alerts in OBS and other tools that support browser sources. It displays subs, gifts, raids, donations, rewards and other activity alerts from your connected platforms.

Activity Alerts editor screenshot.
  1. Open the Synchra Dashboard.
  2. Go to Widgets.
  3. Create an Activity Alerts widget.
  4. Click Widget URL and copy the URL.
  5. Add it to OBS as a Browser Source.
  6. Set the source size to match your stream, for example 1920x1080, 800x600 or whatever fits your stream layout.

Start by disabling the activity types you don’t want an alert for. Pick a theme, adjust colors, set up sound, media and TTS voices.

Create variants when an alert needs special behavior, such as large donations, long resubs.

Media, sound, TTS and variant packs make shared alert assets easy to manage across platforms.

Default pack slots are included for follows, subscriptions, gifted subscriptions, donations, gifts, raids and redeems. Customize a default pack once, and every variant using that pack gets the update. Variants can still add their own single media items, sounds or TTS voices on top. Variant packs can refine a variant after it matches, for example to share special tier, amount or keyword versions across multiple rules.

  • Alert text for each activity type.
  • Colors, font size, max width, duration and animations.
  • Media placement above, below, beside, behind or in front of text.
  • Independent title and message positioning, display delays and entrance animations.
  • Reusable themes for a consistent look.
  • Reusable media, sound, TTS and variant packs for shared alert assets.
  • Image or video media.
  • Separate alert sounds, media sounds, or imports from Sound Alerts.
  • Text-to-speech from connected TTS providers.
  • Custom TypeScript scripts for advanced behavior.
  • Variants for different amounts, counts or sub types.
  • Chance controls for variants, media, sounds and TTS voices.
  • Queue delay between alerts.
  • Pause, mute, skip, play next and replay controls.
  • Export and import widget settings as JSON.
PartWhat it does
GroupA full alert setup. Only the active group plays. Use groups for normal, event or seasonal alert packs.
RuleMatches one alert type, such as Twitch raid or Streamlabs donation.
VariantA version of a rule with its own text, filters, media, sound, TTS and CSS.
ThemeReusable appearance, layout, timing and CSS settings.
Media, sound or TTS packReusable assets shared by variants across providers.
Variant packReusable refinements applied after a rule variant matches.

When activity comes in, Synchra checks the active group, finds the matching rule, then picks the best matching variant. If that variant has active variant packs, Synchra then picks the best matching pack variant and merges it over the fallback settings from the matched rule variant. If multiple variants or pack items match equally well, chance decides which one plays.

When a variant pack is active, the editor labels the rule variant’s theme, appearance, and layout sections as Fallback. Those values apply only when the matching pack variant does not override them.

Variants let one alert type have multiple versions.

Useful examples:

  • Small donation: simple alert.
  • Large donation: bigger animation and TTS.
  • Resub from 1 to 5 months: normal alert.
  • Resub from 12 months and up: special alert.
  • Twitch sub tier 3000: premium alert.

Filters can use value ranges and sub types when the provider sends that data. For example, Twitch subs can filter by Prime, T1, T2 or T3. Filters can also use message keywords for activity types that include a viewer message. More specific matches win before chance is applied, so a high-chance generic variant will not override a matching specific variant.

The title and message fields support variables.

VariableMeaning
{user} or {viewer}Viewer display name.
{viewer_name}Viewer username.
{recipient}Gift recipient display name, when available.
{recipient_name}Gift recipient username, when available.
{amount}Formatted money amount, when available.
{value}Number of months, subs, bits, viewers, etc.
{unit}Months, subs, bits, etc.
{type}Activity type display name.
{subtype}Sub type display name, such as a sub tier, redeem name, .
{provider}Activity provider, such as twitch or youtube.
{message}Viewer message, when available.
{system_message}Provider/system message, when available.

Examples:

{user} just subscribed!
{user} donated {amount}
{user} raided with {value} viewers
Thanks for the {value} {unit}, {user}!

Use Media placement to put selected image or video media above, below, to either side of, behind, or in front of the alert text.

In the variant editor, drag the title or message directly in the design preview to position it. The corresponding X position and Y position fields update when you release it. Use those fields when you need an exact value or cannot use dragging:

  • Positive X moves right; negative X moves left.
  • Positive Y moves down; negative Y moves up.
  • Set both values to 0 to use the normal position.

Title and message positions are independent. Each also has its own Entrance animation and Show delay. The selected animation starts after that text element’s delay, so the title and message can enter at different times with different effects.

The available entrance animations are None, Fade, Slide up, Slide down, Slide left, Slide right, Pop, Bounce, Flip, Zoom, Spin, Drop, Swing, Blur and Glitch. None leaves text unanimated when its delay is 0. With a nonzero delay it uses a short reveal after the delay, preserving the existing delayed-text behavior.

Text delays and entrance animations run only during alert playback. The design preview keeps the text visible so it can still be styled and positioned.

Use the independent Text alignment controls to align the title or message left, center or right within the available text area. Alignment changes how text sits inside its layout area; X and Y position move that area.

Layout and timing settings can be defined on themes, groups, rule variants and variant-pack variants. More specific values override inherited values.

Media can be an image or video URL. Sounds can be separate sound URLs, or attached directly to a media item with Sound URL.

For reusable assets, put media in media packs and sounds in sound packs. If the selected media item has Sound URL, that sound plays instead of the selected separate sound. If the selected media is a video and no separate sound is selected, the video can use its embedded audio; otherwise the video is muted so audio sources do not overlap.

TTS uses connected TTS providers, such as TTSMonster, ElevenLabs or Amazon Polly. Create one or more TTS voices, choose what they read, then reuse them through TTS packs.

Alert sound plays first. TTS starts after the sound finishes. If you set a TTS start delay, that delay is added after the sound finishes.

Start with the built-in settings first. Use custom CSS when you want a specific layout, border, font, glow, background or animation detail.

CSS order: built-in CSS, Theme CSS, Group CSS, then Variant CSS. Later rules win at equal specificity.

Simplified generated markup; optional elements are marked.

<div class="activity-alert-widget">
  <div
    class="activity-alert-celebration"
    data-celebration-type="confetti"
  >
    <div class="activity-alert-celebration__particle"></div>
    <!-- More generated particles -->
  </div>

  <div
    class="activity-alert activity-alert--media-above activity-alert--pop"
    data-alert-group="Default alerts"
    data-alert-rule="raid"
    data-alert-variant="Large raid"
  >
    <div class="activity-alert__layout">
      <img
        class="activity-alert__media activity-alert__media--image"
        alt=""
      /> <!-- Optional; can be a video instead -->
      <div class="activity-alert__text"> <!-- Optional -->
        <div class="activity-alert__title"> <!-- Optional -->
          <div
            class="activity-alert__content activity-alert__content--title activity-alert--slide_left"
          >
            Welcome
            <span class="activity-alert__var activity-alert__var--user">
              Viewer name
            </span>
          </div>
        </div>
        <div class="activity-alert__message"> <!-- Optional -->
          <div
            class="activity-alert__content activity-alert__content--message activity-alert--pop"
          >
            Thank you for the raid of
            <span class="activity-alert__var activity-alert__var--amount">
              250
            </span>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

Layout: full-size centered root; media placement changes the inner layout between column, row and layered grid. Alert entry and exit classes animate .activity-alert. Independent title and message entry classes animate their .activity-alert__content elements, leaving positioning transforms on .activity-alert__title and .activity-alert__message.

ClassDescription
.activity-alert-widgetFull-canvas root. It centers the alert and contains the celebration as a sibling overlay.
.activity-alertMain alert container. It owns the resolved variables and animation/state classes.
.activity-alert__layoutInner layout containing optional media and text. Media-placement classes change its direction or grid layering.
.activity-alert__mediaShared class on an optional <img> or <video>. Built-in sizing uses object-fit: contain.
.activity-alert__media--imageAdded when the media element is an image.
.activity-alert__media--videoAdded when the media element is a video.
.activity-alert__textOptional column containing the title and message.
.activity-alert__titleOptional title template. Long content wraps.
.activity-alert__messageOptional message template. Long content wraps.
.activity-alert__contentInner title or message layer that receives its entrance animation.
.activity-alert__content--titleIdentifies the title animation layer and applies the title delay variable.
.activity-alert__content--messageIdentifies the message animation layer and applies the message delay variable.
.activity-alert__content--revealUsed for the basic delayed reveal when the text entrance animation is None.
.activity-alert--<animation>Applies a built-in animation. Values are none, fade, slide_up, slide_down, slide_left, slide_right, pop, bounce, flip, zoom, spin, drop, swing, blur and glitch. The class can appear on the main alert or a text content layer.
.activity-alert__varInline <span> around any rendered template variable.
.activity-alert__var--user{user} variable.
.activity-alert__var--viewer{viewer} variable.
.activity-alert__var--recipient{recipient} variable.
.activity-alert__var--amount{amount} variable.
.activity-alert__var--value{value} variable.
.activity-alert__var--count{count} variable.
.activity-alert__var--unit{unit} variable.
.activity-alert__var--count_name{count_name} variable.
.activity-alert__var--subtype{subtype} variable.
.activity-alert-celebrationFull-canvas celebration layer. It is always present but can be empty.
.activity-alert-celebration__particleRepeated particles inserted during a celebration. Particle elements receive inline position and animation styles.

The main alert also gets a media-placement class: .activity-alert--media-above, .activity-alert--media-below, .activity-alert--media-left, .activity-alert--media-right, .activity-alert--media-behind, or .activity-alert--media-front.

Every rendered template variable gets a matching modifier class based on the variable name. For example, {viewer_name} gets .activity-alert__var--viewer_name.

AttributeElementUse
data-alert-group="<group-name>".activity-alertTarget the active group by its user-defined name.
data-alert-rule="raid".activity-alertTarget an activity type, such as raid, sub or donation.
data-alert-variant="<variant-name>".activity-alertTarget the selected variant by its user-defined name.
data-celebration-type="confetti".activity-alert-celebrationTarget a celebration such as confetti, snow, lunarNewYear or cherryBlossom. It is empty when no celebration runs.

Group and variant values are names entered by the user. Match the exact value in a quoted attribute selector.

VariableValue typeControls
--activity-alert-font-familyFont family listFont for the entire alert.
--activity-alert-text-colorCSS colorDefault alert text.
--activity-alert-user-colorCSS colorUser, viewer and recipient template variables.
--activity-alert-viewer-colorCSS colorCompatibility fallback for viewer-related variables. It normally matches the user color.
--activity-alert-count-colorCSS colorAmount, count and value template variables.
--activity-alert-count-name-colorCSS colorUnit, count-name and subtype template variables.
--activity-alert-max-widthCSS lengthMaximum width of the main alert. Defaults to the available width when unset.
--activity-alert-font-sizeCSS lengthTitle font size.
--activity-alert-message-font-sizeCSS lengthMessage font size.
--activity-alert-title-offset-xCSS lengthHorizontal title translation.
--activity-alert-title-offset-yCSS lengthVertical title translation.
--activity-alert-title-alignmentleft, center or rightTitle text alignment.
--activity-alert-title-delayCSS timeDelay before the title entrance animation or basic reveal starts.
--activity-alert-message-offset-xCSS lengthHorizontal message translation.
--activity-alert-message-offset-yCSS lengthVertical message translation.
--activity-alert-message-alignmentleft, center or rightMessage text alignment.
--activity-alert-message-delayCSS timeDelay before the message entrance animation or basic reveal starts.

These variables are inline on .activity-alert. Reuse them as tokens; override element properties instead of redeclaring the variables.

Get a font import, then:

@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap");

.activity-alert {
  font-family: "Roboto", sans-serif;
}
.activity-alert {
  padding: 24px 34px;
  border: 3px solid #00d4ff;
  border-radius: 10px;
  background: rgba(8, 12, 20, 0.92);
  box-shadow: 0 0 28px rgba(0, 212, 255, 0.45);
}

.activity-alert__title {
  color: #ffffff;
  text-transform: uppercase;
}

.activity-alert__message {
  color: #d8f7ff;
}
.activity-alert__var--user {
  color: var(--activity-alert-user-color);
  text-shadow: 0 0 14px currentColor;
}

.activity-alert__var--amount,
.activity-alert__var--value {
  color: var(--activity-alert-count-color);
  font-size: 1.15em;
}
.activity-alert[data-alert-rule="raid"] {
  border-color: #ff4d8d;
}

.activity-alert[data-alert-rule="raid"] .activity-alert__title::after {
  content: " incoming!";
}
.activity-alert {
  gap: 18px;
}

.activity-alert__media {
  max-width: min(80dvw, 620px);
  max-height: 45dvh;
}
.activity-alert-celebration[data-celebration-type="confetti"]
  .activity-alert-celebration__particle {
  filter: drop-shadow(0 0 6px white);
}

Custom scripts add behavior to the normal alert. They do not replace the built-in renderer, so the title template, message template, media, sounds, CSS and animations still work unless your script changes the DOM itself.

Scripts can be added in three places: Theme, Group and Variant.

When more than one script applies, Synchra loads them in this order: Theme script > Group script > Variant script. The selected variant theme is used when the variant has its own theme; otherwise the group theme is used.

Each script uses synchra.defineAlertScript. onShow runs once after the alert DOM is rendered.

synchra.defineAlertScript({
  onShow(ctx) {
    ctx.elements.alert?.classList.add("is-special");
  },
});
HookWhen it runs
onShow(ctx)Once when the alert is rendered. Can return a cleanup function.
onExit(ctx)When the alert starts exiting.
onPreview(ctx)After onShow when the alert is rendered without live playback.
onCleanup(ctx)When the alert is removed. Cleanup functions returned from onShow also run.

The ctx object gives the script access to the current alert:

PropertyMeaning
ctx.activityThe raw activity event.
ctx.varsThe same formatted values used by text templates, such as user, amount, value and message.
ctx.itemIdThe unique ID for this rendered alert item.
ctx.elements.rootThe widget root element.
ctx.elements.alertThe main .activity-alert element.
ctx.elements.titleThe title element, if rendered.
ctx.elements.messageThe message element, if rendered.
ctx.elements.mediaThe media element, if rendered.
ctx.groupThe active alert group.
ctx.ruleThe matched alert rule.
ctx.variantThe selected variant.
ctx.mediaAssetThe selected image or video asset, if any.
ctx.soundAssetThe selected sound asset, if any.
ctx.ttsAssetThe selected TTS asset, if any.
ctx.settingsThe resolved visual, TTS, CSS and script settings for this alert.
ctx.exitingtrue during exit handling.
ctx.playbacktrue during normal widget playback.

The resolved text timing settings are available to scripts:

PropertyMeaning
ctx.settings.title_animation_typeResolved title entrance animation name.
ctx.settings.title_delay_secondsSeconds before the title entrance starts.
ctx.settings.message_animation_typeResolved message entrance animation name.
ctx.settings.message_delay_secondsSeconds before the message entrance starts.

The context also exposes a shared registry for scripts on the same alert:

MethodUse
ctx.provide(name, value)Store one helper value.
ctx.provide({ ... })Store multiple helper values.
ctx.get(name)Read a helper value, or undefined if it does not exist.
ctx.require(name)Read a helper value and throw if it does not exist.
ctx.has(name)Check whether a helper value exists.
ctx.remove(name)Remove a helper value.
ctx.helpers<T>()Read helpers through a typed object.
synchra.defineAlertScript({
  async onShow(ctx) {
    const response = await fetch(
      "https://jsonplaceholder.typicode.com/users/1",
    );
    const profile = await response.json();

    if (ctx.elements.message) {
      ctx.elements.message.textContent += ` - ${profile.name}`;
    }
  },
});

If you want to take over the markup completely, replace the widget root HTML.

synchra.defineAlertScript({
  onShow(ctx) {
    ctx.elements.root.innerHTML = `
      <div class="custom-alert">
        <img class="custom-alert__avatar" alt="" />
        <div>
          <div class="custom-alert__title"></div>
          <div class="custom-alert__message"></div>
        </div>
      </div>
    `;
  },
});

Return a cleanup function from onShow when the script starts timers, animations or event listeners.

synchra.defineAlertScript({
  onShow(ctx) {
    const timer = window.setInterval(() => {
      ctx.elements.alert?.classList.toggle("pulse");
    }, 300);

    return () => window.clearInterval(timer);
  },
});

Theme, group and variant scripts for the same alert share an in-memory helper registry. This registry only exists while that alert is being shown; it is not a database and it is not persisted.

Use ctx.provide in a theme or group script:

synchra.defineAlertScript({
  onShow(ctx) {
    ctx.provide({
      formatCoins(value: number) {
        return `${value.toLocaleString()} coins`;
      },
      async loadViewer(viewer: string) {
        const response = await fetch(
          `https://example.com/api/viewers/${viewer}`,
        );
        return response.json();
      },
    });
  },
});

Use the helpers in a variant script:

type Helpers = {
  formatCoins(value: number): string;
  loadViewer(viewer: string): Promise<{ level: number }>;
};

synchra.defineAlertScript({
  async onShow(ctx) {
    const helpers = ctx.helpers<Helpers>();
    const viewer = await helpers.loadViewer(ctx.activity.viewer_name);

    if (ctx.elements.message) {
      ctx.elements.message.textContent =
        `${ctx.vars.user} is level ${viewer.level} with ` +
        helpers.formatCoins(ctx.activity.count);
    }
  },
});