Fix Braze session handling and banner eligibility refresh logic#15932
Open
andresilva-guardian wants to merge 6 commits into
Open
Fix Braze session handling and banner eligibility refresh logic#15932andresilva-guardian wants to merge 6 commits into
andresilva-guardian wants to merge 6 commits into
Conversation
|
Hello 👋! When you're ready to run Chromatic, please apply the You will need to reapply the label each time you want to run Chromatic. |
juabara
approved these changes
May 20, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
sessionTimeoutInSecondsin initialiseBraze.ts from1second to1800seconds (30 minutes, the Braze SDK default).Change 2: We add a
document.visibilitychangelistener in buildBrazeMessaging.ts that re-firesrequestBannersRefreshwhenever 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:
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
requestBannersRefreshcall: 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
logBannerImpressionsis called, Braze advances them to the next Canvas step. On the nextrequestBannersRefreshcall (the next page load), the user is no longer at the banner delivery step, so Braze returnsnull. The banner will not appear again until the Canvas re-entry criteria are met and a newStart Sessionevent triggers re-entry into the Canvas.Why
sessionTimeoutInSeconds: 1was the amplifierThe Braze SDK fires a "Start Session" event whenever a new session opens, and it opens a new session after
sessionTimeoutInSecondsof user inactivity. With the previous value of1second, 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
requestBannersRefreshdrops. The Braze SDK enforces a "once per session" constraint onrequestBannersRefresh. 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. TherequestBannersRefreshcall 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
requestBannersRefreshonvisibilitychangeThe Braze SDK manages subsequent session starts silently. Once the page has initialised and
openSession()has been called, the SDK monitorsdocument.visibilitychangeand other activity signals internally. AftersessionTimeoutInSecondsof 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
subscribeToSessionUpdatesin 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
visibilitychangeas 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 callingrequestBannersRefreshonvisibilitychangeis 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, whichrefreshBannershandles 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
sessionTimeoutInSecondsBraze 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
visibilitychangelistener, 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
requestBannersRefreshis firing at the right moments.