Embedding Chatterfly

Add interactive workflow and HITL (human-in-the-loop) sessions to any website or third-party application using the @chatterfly/widget React component.

Overview

The widget lets external participants — customers, reviewers, survey respondents — interact with a running Chatterfly workflow without needing a Chatterfly account. It connects directly to the backend over a WebSocket, renders any HITL session that appears on the run (chat, form, input fields), and hands results back to the workflow automatically.

The package is framework-agnostic React and has zero runtime dependencies beyond react and react-dom. It ships CSS Modules styles in a separate style.css export that you import once.

How it works

  1. Your server starts a workflow run via POST /api/v1/runs and mints a short-lived participant token scoped to that run.
  2. Your server passes the runId and participantToken to the browser (e.g. embedded in your page HTML or returned from an API call).
  3. The <ChatterflyWidget> component opens a WebSocket to the Chatterfly backend and streams live run events.
  4. When the workflow reaches a HITL step, the widget automatically renders the appropriate UI — a chat window, a form, or individual input fields.
  5. Submitted responses are sent back to the backend and the workflow resumes. The widget transitions to a completion state when the run finishes.

Installation

Install the package from npm:

bash
npm install @chatterfly/widget

Import the stylesheet once in your app entry point:

js
import "@chatterfly/widget/style.css";

Minting a participant token

Participant tokens are short-lived JWTs scoped to a single run. They must be issued server-side using your API key — never from the browser.

Note: Prefer a stable workflow key (starts with cfw_, minted from the Ship tab). Unlike per-deployment keys (cf_), it survives redeploys and resolves to the workflow's latest active deployment — so your backend never changes when you redeploy. Pass workflow_id in the body; only add deployment_id to pin a specific snapshot.

1. Start a run (optional)

If you are starting the run from your own backend:

http
POST https://your-chatterfly.example.com/api/v1/runs
Authorization: Bearer <YOUR_API_KEY>
Content-Type: application/json

{
  "workflow_id": "wf_abc123",
  "input": { "name": "Alice", "issue": "Billing query" }
}

Response:

json
{ "run_id": "run_xyz789" }

2. Mint a participant token

http
POST https://your-chatterfly.example.com/api/v1/runs/run_xyz789/tokens
Authorization: Bearer <YOUR_API_KEY>
Content-Type: application/json

{
  "role": "customer"
}

Response:

json
{ "participant_token": "eyJhbGci..." }
Note: Tokens expire after 1 hour. Re-mint as needed for long-running sessions.

Basic usage

tsx
import "@chatterfly/widget/style.css";
import { ChatterflyWidget } from "@chatterfly/widget";

export function SupportChat({ runId, token }: { runId: string; token: string }) {
  return (
    <ChatterflyWidget
      runId={runId}
      participantToken={token}
      apiBase="https://your-chatterfly.example.com"
      onCompleted={(output) => console.log("Done:", output)}
      onError={(err) => console.error("Error:", err)}
    />
  );
}

The widget is self-contained and responsive. It adapts its height to content and fills the width of its container.

Configuration & props

PropTypeRequiredDescription
runIdstringYes*The workflow run to connect to. *Not required with workflowId/deploymentId.
participantTokenstringYes*JWT issued by POST /runs/:id/tokens. *Not required with workflowId/deploymentId.
workflowIdstringNoServerless auto-start; resolves the latest live deployment, survives redeploys.
apiBasestringYesBackend base URL. Required — no localhost default.
initialInputRecord<string, unknown>NoSeeds the run's declared inputs on auto-start.
classNamestringNoExtra CSS class applied to the root element.
onCompleted(output?: Record<string, unknown>) => voidNoCalled when the run reaches a terminal completed state, with the final run state.
onError(err: WidgetError) => voidNoCalled on connection, config, or runtime errors. See Events & errors below.
persistSessionboolean | { storage?, key? }NoResume across page refresh for serverless embeds. Default true (sessionStorage) — pass false to opt out.
clickToStartboolean | { label?, description?, icon? }NoGate auto-start behind a “Start conversation” button instead of minting a run on mount.
resumePromptboolean | { title?, continueLabel?, restartLabel? }NoShow a continue/restart choice card instead of silently resuming a persisted active session.
showCompletedHistorybooleanNoReplay a persisted terminal run read-only with a “Start new conversation” CTA. Default true.
connectionPolicy{ idleDisconnectMs?, pollIntervalMs?, hibernateAfterMs? } | "always-on"NoTunes the dormant/hibernating connection lifecycle. See Connection Lifecycle.

Events & errors

Beyond onCompleted/onError, the widget exposes a firehose and granular callbacks, plus an imperative ref:

tsx
const ref = useRef<ChatterflyWidgetHandle>(null);

<ChatterflyWidget
  ref={ref}
  {...props}
  onStatusChange={(status) => setStatus(status)}
  onSessionOpened={(session) => track("hitl_session_opened", session)}
  onMessage={(msg) => track("bot_message", msg)}
  onEvent={(event) => analytics.track(event.type, event)}
/>

ref.current?.restart();
ref.current?.disconnect();
ref.current?.sendMessage("hi");

onError receives a WidgetError { code, message } — codes include config_missing_api_base, workflow_not_public, no_live_deployment, network, token_expired, and run_failed. It stringifies to message, so existing onError={(err) => console.error(String(err))} handlers keep working.

Theming

Override the widget's appearance with the typed theme prop, or with CSS custom properties on any ancestor element:

tsx
<ChatterflyWidget {...props} theme={{ primary: "#0ea5e9", radius: "4px" }} />
css
/* In your stylesheet or a <style> tag */
.my-widget-container {
  --cf-primary:  #5B4AE4;   /* core indigo */
  --cf-accent:   #F28B30;   /* signal orange */
  --cf-bg:       #ffffff;
  --cf-fg:       #0C1B3A;
  --cf-border:   rgba(12, 27, 58, 0.12);
  --cf-radius:   10px;
}
Note: All widget styles are scoped under .cf-* CSS Module class names and respect the custom properties above. See the Theming & Slots page for the full token table, dark mode, and chrome slots (header, footer, bubble overrides). For a docked chat-bubble launcher instead of an inline widget, see Floating Launcher. For how the widget manages its WebSocket connection over the run's lifetime (dormancy, reconnects, token expiry), see Connection Lifecycle.

Custom input types

Workflows can specify input_type: "x-signature" (or any x- prefix) for domain-specific fields. Register a React component to handle them:

tsx
import { registerInputType } from "@chatterfly/widget";
import { SignaturePad } from "./SignaturePad";

// Register once, before rendering any widget
registerInputType("x-signature", SignaturePad);

Your component receives:

ts
interface InputComponentProps {
  field: FieldSpec;             // full field definition from the workflow
  value: unknown;               // current value
  onChange: (v: unknown) => void;
  disabled?: boolean;
}

If no matching component is registered, the widget falls back to a plain TextArea.