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 dist/style.css 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/dist/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.

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/dist/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
runIdstringYesThe workflow run to connect to.
participantTokenstringYesJWT issued by POST /runs/:id/tokens.
apiBasestringNoBackend base URL. Defaults to http://localhost:8080.
classNamestringNoExtra CSS class applied to the root element.
onCompleted(output: unknown) => voidNoCalled when the run reaches a terminal completed state.
onError(error: Error) => voidNoCalled on connection or runtime errors.

Theming

Override the widget's appearance with CSS custom properties on any ancestor element:

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.

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.