Skip to main content
Good error handling keeps customers oriented without exposing private routing or enforcement details. Treat the SDK view as the public source of truth, show customer-safe recovery copy, and keep sensitive values out of logs.

Error Surfaces

SurfaceWhere it appearsRecommended response
Setup errorProvider, hook, or SDK initializationShow a fallback panel and report a sanitized app log.
Action errorStart, reroute, enter, book, leave, or reenter actionKeep the current UI visible, disable duplicate submits, and offer retry when appropriate.
Gated stateJourneyView with journeyStage === "gated"Render the required customer input with generic copy.
Denied or unavailable stateRouted sequence viewExplain the outcome without inferring private reasons.
Handoff errorYour admission or checkout endpointKeep the customer on a recoverable page and retry through your own app boundary.

React Widget Errors

ExperienceWidget exposes onError for adapter and action errors. Use it for app-owned logging and customer support signals.
import { ExperienceWidget } from "@fanfare-io/fanfare-sdk-react";

<ExperienceWidget
  experienceId="exp_123"
  autoStart
  onError={(error) => {
    reportClientError({
      name: error.name,
      message: error.message,
    });
  }}
  slots={{
    error: ({ error, onRetry }) => (
      <ErrorPanel message={error} onRetry={onRetry} />
    ),
  }}
/>;
Do not attach admission grants or raw journey snapshots to third-party error reports.

Custom React UI

When you use useExperienceJourney, branch on the hook error and on the current public view.
import { useExperienceJourney } from "@fanfare-io/fanfare-sdk-react";

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

  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} />;
}
For action handlers, disable the submitted control while the promise is pending and keep the last known view rendered if the action fails.

Core SDK Actions

Headless integrations should catch errors at the action boundary.
async function runAction(action: () => Promise<unknown>) {
  setPending(true);
  setError(null);

  try {
    await action();
  } catch (error) {
    setError(error instanceof Error ? error.message : "Something went wrong.");
  } finally {
    setPending(false);
  }
}
Only show controls for actions on the current JourneyView or SequenceView. If the action is not present, the UI should not offer it.

Admission Handoff Failures

When an admitted customer moves to checkout, your app owns the handoff error path.
async function sendAdmission(admissionGrant: string) {
  const response = await fetch("/api/fanfare/admission", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ admissionGrant }),
  });

  if (!response.ok) {
    throw new Error("Checkout is not ready yet. Please try again.");
  }
}
Keep server responses contract-focused. Return a customer-safe status or retry instruction rather than private routing details.

Logging Guidelines

Log enough to diagnose your application boundary:
  • Experience ID.
  • High-level public state name.
  • Your route, release, and request ID.
  • Sanitized error name and message.
  • Whether the handoff was attempted.
Avoid logging:
  • Admission grants.
  • Raw journey snapshots.
  • Customer verification inputs.
  • Private assumptions about routing or enforcement.

Customer Copy

Prefer copy that explains what the customer can do next:
SituationGood copy
Network failure”We could not update your status. Please try again.”
Gated state”Complete this step to continue.”
Denied state”This experience is not available for this session.”
Handoff failure”Checkout is not ready yet. Please try again.”
Ended or unavailable”This experience is no longer available.”
Avoid copy that claims why a customer was routed or denied unless that reason is part of your public customer experience.

Testing

  • Render every public state your UI handles.
  • Force action promises to reject in app-boundary tests.
  • Confirm retry controls call the current view action.
  • Confirm telemetry redacts grants and snapshots.
  • Confirm server handoff failures keep the customer in a recoverable flow.
For examples of controlled app-boundary tests, see Testing Your Integration.