Skip to content

Fix Braze session handling and banner eligibility refresh logic#15932

Open
andresilva-guardian wants to merge 6 commits into
mainfrom
afs/fix/braze-banners-canvas-reentry
Open

Fix Braze session handling and banner eligibility refresh logic#15932
andresilva-guardian wants to merge 6 commits into
mainfrom
afs/fix/braze-banners-canvas-reentry

Conversation

@andresilva-guardian
Copy link
Copy Markdown
Contributor

@andresilva-guardian andresilva-guardian commented May 20, 2026

What does this change?

We fix two root causes behind Braze Banner campaigns not re-appearing for users when the banner is delivered through a Braze Canvas with a "Start Session" entry trigger.

Change 1: We correct sessionTimeoutInSeconds in initialiseBraze.ts from 1 second to 1800 seconds (30 minutes, the Braze SDK default).

Change 2: We add a document.visibilitychange listener in buildBrazeMessaging.ts that re-fires requestBannersRefresh whenever the user returns to the tab, ensuring the SDK requests fresh banner eligibility at the moment a new Braze session is most likely to have started.

Why?

The real-world Canvas that exposed this

We had an active Canvas with the following configuration:

  • Entry trigger: "Start Session" in Web B2C Live
  • Re-entry: allowed after 10 seconds
  • Canvas delay: immediately after trigger criteria are met
  • Exit criteria: none

The expected behaviour was: user opens a page, sees the banner, navigates away, comes back after a short while, and sees the banner again. The observed behaviour was: user sees the banner once, then does not see it again reliably, sometimes for the entire day, and only recovers after several navigations. The same banner served via a standard Braze Campaign worked correctly every time.

Why campaigns work but Canvas does not

A standard Braze Campaign answers a stateless question on each requestBannersRefresh call: is this user in the target segment and within frequency caps? The answer is consistently yes, so the banner is returned on every page load.

A Braze Canvas is a stateful user journey. When the user sees the banner and logBannerImpressions is called, Braze advances them to the next Canvas step. On the next requestBannersRefresh call (the next page load), the user is no longer at the banner delivery step, so Braze returns null. The banner will not appear again until the Canvas re-entry criteria are met and a new Start Session event triggers re-entry into the Canvas.

Why sessionTimeoutInSeconds: 1 was the amplifier

The Braze SDK fires a "Start Session" event whenever a new session opens, and it opens a new session after sessionTimeoutInSeconds of user inactivity. With the previous value of 1 second, the SDK was firing hundreds of "Start Session" events per user per day, one per second of inactivity.

This caused two distinct problems:

Canvas chaos. The Canvas was never designed to cycle at 10-11 second intervals (10 second re-entry window + next 1-second session start). The user was continuously re-entering the Canvas, seeing the banner, and advancing to the next step in rapid succession. Banner availability was entirely unpredictable because the Canvas state was churning faster than any page load could observe it.

Any Canvas that uses session-based delay steps (for example, "wait 2 sessions before sending the next message") was also silently broken. With a 1-second timeout, "2 sessions" meant approximately 2 seconds in practice.

Silent requestBannersRefresh drops. The Braze SDK enforces a "once per session" constraint on requestBannersRefresh. With a 1-second timeout, a fast page reload, meaning the browser finishing the new page load in under 1 second from the moment the previous page unloaded, would be seen by the SDK as still within the previous session. The requestBannersRefresh call at init would be silently dropped (the SDK had already consumed the one allowed call for that session), getBanner() would return the stale cached null from the previous impression, and the banner would not appear. This produced the intermittent behaviour we observed: some users see the banner again, some do not, depending entirely on how fast their browser and network are.

Why requestBannersRefresh on visibilitychange

The Braze SDK manages subsequent session starts silently. Once the page has initialised and openSession() has been called, the SDK monitors document.visibilitychange and other activity signals internally. After sessionTimeoutInSeconds of inactivity, it starts a new session and fires a "Start Session" event to the Braze backend, which can trigger Canvas re-entry and make a banner available again.

The SDK does not expose any session lifecycle hook. There is no subscribeToSessionUpdates in SDK v6.5.0 (we verified this directly from the package type definitions). This means that even when the Canvas has re-entered the user and a banner is ready, the SDK keeps serving its locally cached null, because it was never asked to fetch updated data.

We use visibilitychange as the correct proxy for "user has returned after a potentially session-creating absence." It is the same internal signal the SDK uses to detect inactivity and manage sessions, so calling requestBannersRefresh on visibilitychange is the right moment to ask Braze for updated eligibility. The Braze SDK's built-in token bucket rate limiting (5 tokens per session, 1 refill every 3 minutes) ensures that rapid or accidental tab switches do not burn unnecessary network requests. If no tokens are available, the SDK fires the error callback, which refreshBanners handles gracefully without throwing or blocking the page.

This approach was validated through our open ticket with Braze support. They confirmed: "requestBannersRefresh needs to be called again at the start of each new session, after changeUser has completed."

Risks of changing sessionTimeoutInSeconds

Braze session analytics. Changing from 1 to 1800 will dramatically reduce the session count reported in Braze. The previous value was inflating counts by roughly 1800x. Any dashboards, audience segments, or frequency caps built on session counts will reflect very different numbers after this change. We have to notify CRM to audit their dashboards and any active Canvas or Campaign frequency rules that reference session counts.

Canvas session-based timing. Any active Braze Canvas that uses session-based delay steps will now have very different timing. A "wait 2 sessions" delay that was effectively 2 seconds will now be at minimum 60 minutes. We have to ask CRM to review all active Canvases for session-based timing before this goes to production.

Token bucket consumption. With the 1-second timeout, the token bucket (5 tokens, 1 refill per 3 minutes) was being exhausted within seconds on each page load. With 1800-second sessions and the new visibilitychange listener, we will consume at most 1 token per return-to-tab event, well within the 3-minute refill rate. This is a significant improvement in how we interact with the Braze API rate limits.

Screenshots

This change affects background SDK behaviour and does not produce visual output. We recommend testing manually with logging on the console for "Braze" to observe session lifecycle events and confirm that requestBannersRefresh is firing at the right moments.

@andresilva-guardian andresilva-guardian requested a review from a team as a code owner May 20, 2026 11:52
@github-actions
Copy link
Copy Markdown

Hello 👋! When you're ready to run Chromatic, please apply the run_chromatic label to this PR.

You will need to reapply the label each time you want to run Chromatic.

Click here to see the Chromatic project.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 20, 2026

@andresilva-guardian andresilva-guardian added the fix Departmental tracking: fix label May 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

fix Departmental tracking: fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants