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
- Your server starts a workflow run via
POST /api/v1/runsand mints a short-lived participant token scoped to that run. - Your server passes the
runIdandparticipantTokento the browser (e.g. embedded in your page HTML or returned from an API call). - The
<ChatterflyWidget>component opens a WebSocket to the Chatterfly backend and streams live run events. - When the workflow reaches a HITL step, the widget automatically renders the appropriate UI — a chat window, a form, or individual input fields.
- 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:
npm install @chatterfly/widgetImport the stylesheet once in your app entry point:
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:
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:
{ "run_id": "run_xyz789" }2. Mint a participant token
POST https://your-chatterfly.example.com/api/v1/runs/run_xyz789/tokens
Authorization: Bearer <YOUR_API_KEY>
Content-Type: application/json
{
"role": "customer"
}Response:
{ "participant_token": "eyJhbGci..." }Basic usage
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
| Prop | Type | Required | Description |
|---|---|---|---|
| runId | string | Yes | The workflow run to connect to. |
| participantToken | string | Yes | JWT issued by POST /runs/:id/tokens. |
| apiBase | string | No | Backend base URL. Defaults to http://localhost:8080. |
| className | string | No | Extra CSS class applied to the root element. |
| onCompleted | (output: unknown) => void | No | Called when the run reaches a terminal completed state. |
| onError | (error: Error) => void | No | Called on connection or runtime errors. |
Theming
Override the widget's appearance with CSS custom properties on any ancestor element:
/* 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;
}.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:
import { registerInputType } from "@chatterfly/widget";
import { SignaturePad } from "./SignaturePad";
// Register once, before rendering any widget
registerInputType("x-signature", SignaturePad);Your component receives:
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.
