Skip to main content
Fanfare integrations receive live journey changes through SDK stores and component hooks. Your app should render those public updates directly, then layer in app-owned product, inventory, or checkout updates from your own systems when needed. Do not infer private routing mechanics from update timing. Treat each emitted view as the current public contract for UI and actions.

SDK Update Sources

SourceUse for
journey.view$Normal headless rendering and valid actions.
journey.latestEvent$Toasts, announcements, and high-level analytics triggers.
useExperienceJourneyReact UI that wants the current view and lifecycle handling.
fanfare-journey-changeWeb component hosts that need to observe widget state changes.

React Updates

The React hook updates when the public journey view changes.
import { useExperienceJourney } from "@fanfare-io/fanfare-sdk-react";

export function LiveExperience({ experienceId }: { experienceId: string }) {
  const { view, start, error } = useExperienceJourney(experienceId, {
    autoStart: true,
  });

  if (error) return <ErrorPanel message={error} />;
  if (!view) return <LoadingPanel />;

  if (view.journeyStage === "ready") {
    return <button onClick={() => void start()}>Start</button>;
  }

  if (view.journeyStage === "routing") return <LoadingPanel />;
  if (view.journeyStage === "gated") return <GatePanel view={view} />;

  return <SequencePanel sequence={view.sequence} />;
}
If you need animation, derive it from public view changes in your component state rather than reading private SDK internals.

Core SDK Stores

Headless integrations can subscribe directly to the journey handle.
import initFanfare from "@fanfare-io/fanfare-sdk-core";

const sdk = await initFanfare({
  organizationId: "org_123",
  publishableKey: "pk_live_123",
});

const journey = sdk.journeys.get("exp_123");

const stopViewUpdates = journey.view$.listen((view) => {
  renderJourney(view);
});

const stopEventUpdates = journey.latestEvent$.listen((event) => {
  if (event) {
    announceJourneyEvent(event);
  }
});
Call the unsubscribe functions when the page, route, or embedded surface unmounts.

Web Component Events

For framework-agnostic hosts, listen to custom element events.
<fanfare-experience-widget experience-id="exp_123" auto-start></fanfare-experience-widget>

<script>
  const widget = document.querySelector("fanfare-experience-widget");

  widget.addEventListener("fanfare-journey-change", (event) => {
    updateHostChrome({ experienceId: event.detail.experienceId });
  });

  widget.addEventListener("fanfare-granted", (event) => {
    sendAdmissionToServer(event.detail.grant);
  });

  widget.addEventListener("fanfare-error", (event) => {
    showHostError(event.detail.error.message);
  });
</script>
The event detail can include SDK state for widget coordination. Keep raw state out of analytics and logs unless your policy explicitly sanitizes it.

App-Owned Live Data

Use your own polling, SSE, or WebSocket channel for data outside the Fanfare journey contract, such as product copy, merchandising, or checkout preparation.
const productUpdates = new EventSource("/api/product-updates");

productUpdates.addEventListener("message", (event) => {
  updateProductPanel(JSON.parse(event.data));
});

window.addEventListener("beforeunload", () => {
  productUpdates.close();
});
Keep these channels separate from Fanfare actions. The current journey view still decides which Fanfare action can be shown.

Optimistic UI

Optimistic UI is useful for app-owned controls, but avoid pretending a Fanfare state transition has happened before the SDK view updates. Recommended pattern:
  • Disable the submitted control while the action promise is pending.
  • Keep rendering the last known public view.
  • Show a local spinner or progress affordance.
  • Replace the UI when the next view arrives.
  • Roll back local pending state if the action rejects.

Performance

  • Subscribe once per mounted experience surface.
  • Unsubscribe on unmount.
  • Avoid duplicating SDK providers for the same page area.
  • Debounce app-owned visual effects, not SDK state handling.
  • Keep analytics high-level and sanitized.

Testing

  • Verify view updates change visible UI.
  • Verify pending action state does not expose invalid controls.
  • Verify unmount cleanup stops app-owned listeners.
  • Verify admitted events call your server handoff once.
  • Verify telemetry records public outcomes, not raw snapshots or grants.
For broader guidance, see Testing Your Integration and Core Headless Example.