Embed the widget.
One snippet, anywhere. The widget loads asynchronously, queues calls until the script is ready, and locks itself to the origins you configured in the dashboard.
The snippet
Paste this just before </body> on any page where the widget should appear. Replace apiKey and widgetId with the values shown in your dashboard after you create a widget.
<!-- Reva Chat Widget -->
<script>
(function(w,d,s,o,f,js,fjs){
w['RevaWidget']=o;w[o] = w[o] || function () { (w[o].q = w[o].q || []).push(arguments) };
var link = d.createElement('link');
link.rel = 'stylesheet';
link.href = 'https://reva.reasonvoice.ai/style.css';
d.head.appendChild(link);
js = d.createElement(s), fjs = d.getElementsByTagName(s)[0];
js.id = o; js.src = f; js.async = 1; fjs.parentNode.insertBefore(js, fjs);
}(window, document, 'script', 'reva', 'https://reva.reasonvoice.ai/reasonvoice-widget.js'));
reva('init', {
apiKey: 'reva_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
widgetId: 'widget_xxxxxxxxxxxx',
apiUrl: 'https://api.reasonvoice.ai',
theme: {
primaryColor: '#0A0A0F',
accentColor: '#B0286E',
position: 'bottom-right',
borderRadius: '14px',
},
welcomeMessage: 'Hi! How can we help?',
});
</script>The exact snippet — pre-filled with your real keys and theme — is available on each widget's page in the dashboard under Get embed code.
What it loads
https://reva.reasonvoice.ai/style.css— widget styles (~6 KB gzipped)https://reva.reasonvoice.ai/reasonvoice-widget.js— the widget runtime
Both are served from our edge CDN. The script is loaded with async so it never blocks rendering. Calls to reva(...) made before the script finishes are queued and flushed once it loads.
Are these keys safe to put in client code?
Yes. apiKey values prefixed with reva_ are publishable — same model Stripe uses for pk_live_* or Google Maps for browser keys. They're designed to ship in your HTML.
The server enforces an allow-list of origins per key. A widget with an https://example.com allow-list won't respond to requests with any other Origin header, regardless of who has the key.
sk_live_*) are different — those should never appear in client code. The dashboard never shows them inside an embed snippet.Theme
Every key has a sensible default; pass only the ones you want to override.
| Key | Type | Default | Notes |
|---|---|---|---|
| primaryColor | string | #4F46E5 | Header + send button |
| secondaryColor | string | #E5E7EB | Bot message background |
| accentColor | string | #10B981 | Active state, focus ring |
| textColor | string | #1F2937 | Body text |
| backgroundColor | string | #FFFFFF | Panel background |
| position | string | bottom-right | "bottom-right" | "bottom-left" |
| size | string | medium | "small" | "medium" | "large" |
| borderRadius | string | 12px | Any CSS length |
| fontFamily | string | Inter, sans-serif | Any CSS font stack |
| logoUrl | string | "" | Custom logo on the panel header |
| showLogo | boolean | false | Render logoUrl when true |
Features
| Key | Type | Default | Notes |
|---|---|---|---|
| typingIndicator | boolean | true | "…" while the agent is thinking |
| messageHistory | boolean | true | Persist messages across page loads |
| userFeedback | boolean | true | 👍/👎 on each agent reply |
| streaming | boolean | true | Token-by-token rendering via SSE |
| fileUpload | boolean | false | Image/document attachments |
| hideBranding | boolean | false | Hide "Powered by ReasonVoice" footer |
hideBranding is on by default for paid plans only. It removes the small “Powered by ReasonVoice” line at the foot of the panel.
Welcome + placeholder
welcomeMessage is the first message shown when a visitor opens the panel for the first time. placeholderText is the empty-state hint inside the input box. Both accept any string; we escape them safely so quotes and emoji are fine.
Verify it’s working
- Load the page on a domain that's in your widget's allow-list.
- Open DevTools → Console. You should see no errors and a successful
GET /api/widgets/{id}/public-config. - Click the launcher and send a test message.
If the panel opens but messages don't send, the most common cause is the page's origin not being in the widget's allow-list. Add it in the dashboard under Widget settings → Allowed origins.
Removing the widget
To remove the widget from a page, delete the snippet. There's no global cleanup function to call — the widget mounts once on script load and unmounts when the page unloads.
Next
Voice agents, voice cloning, and the full API reference are coming soon. In the meantime, write to the founder if you have a question we haven't answered. Get in touch.