@choirhq/widget
v0.1.6
Published
Embeddable Sela chat widget for Choir commerce partners — one component, ticket → chat → payment link → confirmation, and the merchant sees the whole conversation in Choir Inbox.
Maintainers
Readme
@choirhq/widget
Embeddable Sela chat for Choir commerce partners. Drop it into any site — a ticket landing page, an event storefront, a product catalog — and visitors can chat with Sela, receive a Paystack link, and buy inside the widget. The merchant sees the whole conversation in Choir Inbox on web + mobile as it happens.
Ships two entry points:
- React —
import { ChoirChat } from '@choirhq/widget' - Vanilla
<script>—Choir.chat.mount('#el', {...})
Install
npm install @choirhq/widgetReact usage
import { ChoirChat } from '@choirhq/widget';
export default function TicketPage({ ticket }) {
return (
<ChoirChat
baseUrl="https://api.choirworkspace.com"
// Recommended: the workspace slug — human-readable, stable, and
// already public via the CAP JWKS URL. Prefer this over
// `workspaceId` (which still works if you already wired the UUID).
workspaceSlug="miss-intercontinental-zambia"
catalogProductId={ticket.id}
placeholder={`Ask us anything about the ${ticket.name}…`}
onOrderPaid={(order) => {
// Fires when the customer's Paystack checkout completes.
// Widget itself will keep showing the confirmation card; use
// this to navigate to your own receipt page if you have one.
window.location.href = `/tickets/${order.order_id}`;
}}
/>
);
}Vanilla usage
<script src="https://cdn.jsdelivr.net/npm/@choirhq/widget/dist/bootstrap.js" defer></script>
<div id="choir-chat" style="max-width:480px;margin:0 auto"></div>
<script>
Choir.chat.mount('#choir-chat', {
baseUrl: 'https://api.choirworkspace.com',
// Recommended: the workspace slug. `workspaceId` (UUID) still works.
workspaceSlug: 'miss-intercontinental-zambia',
catalogProductId: 'tkt_grand_finale_vip',
onOrderPaid: (order) => {
window.location.href = '/tickets/' + order.order_id;
},
});
</script>Props
| Prop | Type | Notes |
|---|---|---|
| baseUrl | string — required | Choir API root, e.g. https://api.choirworkspace.com. |
| workspaceSlug | string — recommended | Human-readable workspace slug (e.g. miss-intercontinental-zambia). Same value that appears in the CAP JWKS URL. Prefer this over workspaceId. |
| workspaceId | string — alternative | UUID from Workspaces → Settings → Advanced. Kept for partners who already wired the id. Pass at least one of workspaceSlug or workspaceId. |
| catalogProductId | string — optional | If the visitor clicked a specific ticket, pass the product id. Sela's first reply confirms that exact ticket instead of re-asking what they want. |
| customerHint | { externalSource, externalRef, displayName? } — optional | Partner-supplied identity. A repeat visit under the same identity resumes the same customer + conversation. |
| onOrderPaid | (order) => void — optional | Fires on the COMMERCE_ORDER_PAID socket event. Typical use: navigate to a receipt / ticket-PDF page. |
| onError | (err) => void — optional | Unrecoverable errors (network down, token refused). Widget also shows an inline banner. |
| placeholder | string — optional | Composer placeholder text. |
| theme | 'light' \| 'dark' \| 'auto' — optional (default 'auto') | Colour scheme. Auto respects prefers-color-scheme. |
What the visitor sees
Right-aligned Concord-indigo bubbles for their own messages. Left- aligned warm-cream bubbles for Sela and any merchant relay. Payment link renders as a centred card with an "Open checkout →" button; payment received arrives as a green confirmation card. All matches the merchant-side Commerce Conversation view on mobile — customer and merchant see the same shapes.
What the merchant sees
Every widget conversation lands in the existing Choir Commerce Inbox
(/workspaces/<id>/commerce on web, /commerce on mobile) as a row
with source WEB. Nothing new to install merchant-side; they reply
from the same inbox they already use for WhatsApp / Instagram leads.
Security
Widget sessions get a short-lived scoped JWT (15 min) that grants subscribe-only rights to one conversation and send-as-that-one- customer rights. It's never a Choir user token. The site never handles Choir user credentials; the widget never handles anything more sensitive than the browser's own session cookie.
Version
0.1.3 — current stable. What ships in 0.1.x:
- 0.1.0 — text chat + payment-link display +
onOrderPaidcallback + repeat-visit resume vialocalStorage. - 0.1.1 — image / file uploads from the visitor (the attach
button in the composer; same OCR +
attach_payment_proofpipeline as WhatsApp inbound); inline ticket QR rendering on paid receipts (client-side SVG viaqrcode, no WhatsApp media URL dependency). - 0.1.2 — React is now correctly externalized from the library
build. 0.1.0 and 0.1.1 bundled React into
dist/index.*.js, which crashed with "Invalid hook call" whenever the host app also had its own React tree. If you tried 0.1.0 or 0.1.1, upgrade. - 0.1.3 — bootstrap emits an IIFE instead of an ES module, so
the documented
<script src=".../bootstrap.js">drop-in works under a bare<script>tag (notype="module"required). 0.1.1 and 0.1.2 emitted ESM here and neededtype="module"to run. Also ships@types/qrcodeas a devDep (was leaking into dependencies). - 0.1.4 —
workspaceSlugprop as an alternative toworkspaceId. The slug is the stable, public identifier already exposed via the CAP JWKS URL, and it survives a UUID rotation. Backend accepts either; slug wins when both are provided.workspaceIdremains fully supported. - 0.1.6 — three visitor-experience fixes, needs the paired backend
deploy (
source: 'web'autonomous dispatch + widget-sendclient_refecho) to be fully effective. Sela's autonomous reply now reaches the visitor over the socket (previouslydispatchAutonomoushad nowebbranch and posted a "channel doesn't support autonomous send" handoff instead). The widget drops operator-facing system messages (sela_handoff,widget_context, drafts) instead of rendering them as if they were Sela replies. And aclient_refcorrelation id makes the send flow reconcile the optimistic bubble with the server echo, so the visitor's own message doesn't double-render. - 0.1.5 — Socket.IO client now connects to the
/wsnamespace (same one the mobile app has used since day one). 0.1.0–0.1.4 connected to the default namespace, which Engine.IO accepts but where no gateway handlers are bound;subscribe_channelnever got an ack and no Sela reply ever reached the widget. Anyone on ≤ 0.1.4 must upgrade — visible symptom was a UI stuck on "Reconnecting…" with no incoming messages.
Roadmap:
- 0.2:
customerHintidentity merge across devices (Google OAuth handoff). - 0.3: partial hosted-page fallback for very restrictive CSP hosts.
- 0.4: analytics events into existing metrics pipeline + workspace-level rate limiting on session bootstrap.
Getting your workspace_id
Log into choirworkspace.com, open your workspace, Settings →
Advanced. The UUID under "Workspace ID" is what you pass to
workspaceId.
License
MIT — see LICENSE at the repo root.
