> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fanfare.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Error Handling

> Handle SDK, journey, and handoff errors from the public integration contract.

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

| Surface                     | Where it appears                                      | Recommended response                                                                      |
| --------------------------- | ----------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| Setup error                 | Provider, hook, or SDK initialization                 | Show a fallback panel and report a sanitized app log.                                     |
| Action error                | Start, reroute, enter, book, leave, or reenter action | Keep the current UI visible, disable duplicate submits, and offer retry when appropriate. |
| Gated state                 | `JourneyView` with `journeyStage === "gated"`         | Render the required customer input with generic copy.                                     |
| Denied or unavailable state | Routed sequence view                                  | Explain the outcome without inferring private reasons.                                    |
| Handoff error               | Your admission or checkout endpoint                   | Keep 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.

```tsx theme={null}
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.

```tsx theme={null}
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.

```ts theme={null}
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.

```ts theme={null}
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:

| Situation            | Good 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](/getting-started/testing).
