Build
Embed on your website
One line of HTML puts a talk button on your website. A visitor presses it, allows the microphone and is speaking to your agent. There is no server of yours in between.
Switch it on
In the studio, open the agent, go to Integrate and turn on Call button. The page then shows the line to paste, with the agent’s publishable key in it. By API it is the agent’s embed setting:
curl -X PATCH "https://goblins-call-api.onrender.com/v1/agents/ua_3f9a1c2b" \
-H "Authorization: Bearer $CRACKLE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"embed": {
"enabled": true,
"allowedOrigins": [
"example.com"
]
}
}'The agent in the response carries its publicKey, which starts with pk_.
Add it to a page
<script src="https://YOUR_STUDIO_ADDRESS/embed.js" data-agent="pk_your_publishable_key" async></script>That is all of it. The button sits in a corner of the page; these attributes on the same tag change how it looks:
data-agentstringThe agent’s publishable key.
data-labelstringdefault Talk to usThe words on the button.
data-colorhex colourdefault #111111The button’s colour. Its text turns black or white to suit.
data-positionenumbottom-right · bottom-leftWhich corner it sits in.
data-buttonbooleandefault truefalse draws nothing, for a page that has its own button.
A publishable key is safe to show
Anyone can read a key that sits in a web page, so this one is built for that. It can only start a call with the agent exactly as you saved it: the page cannot change the instructions, pass variables or choose who is billed. What it can cost you is bounded by the agent’s embed settings:
allowedOriginsstring[]up to 20The sites the key works on, such as example.com (which covers www.example.com) or *.example.com. Empty allows any site. An entry that is not a site is refused with 400.
maxCallSecondsinteger30–3,600; default 300The longest one call from the button may run.
dailySpendLimitnumberdefault 5The most the button’s calls may cost in a day, in your workspace’s currency. Once it is reached the button says the agent is unavailable until the next day (UTC).
One visitor can also start only so many calls in an hour. If a key is being misused anyway, replace it with Rotate the publishable key: pages carrying the old one stop working at once.
Your own button
The same script gives the page a small API, Crackle, for when you want your own button or want to show what is being said.
<button id="talk">Talk to us</button>
<p id="status"></p>
<script src="https://YOUR_STUDIO_ADDRESS/embed.js"></script>
<script>
Crackle.init({ agent: 'pk_your_publishable_key', button: false });
const talk = document.getElementById('talk');
talk.onclick = () => (Crackle.state === 'idle' ? Crackle.start() : Crackle.end());
Crackle.on('state', (state) => {
// idle, connecting, listening, thinking or speaking
document.getElementById('status').textContent = state === 'idle' ? '' : state;
talk.textContent = state === 'idle' ? 'Talk to us' : 'End call';
});
Crackle.on('transcript', ({ role, text, final }) => console.log(role, text, final));
Crackle.on('error', ({ code, message }) => alert(message));
</script>| Call | What it does |
|---|---|
Crackle.init(options) | Sets the key and the look. Takes agent, button, label, color and position. |
Crackle.start() | Asks for the microphone and starts a call. Call it from a click or a tap. |
Crackle.end() | Hangs up. |
Crackle.mute(on?) | Mutes or unmutes the visitor’s microphone; with no argument it toggles. |
Crackle.on(event, fn) | Listens for state, transcript, started, ended or error. Returns a function that stops listening. |
Crackle.state | idle, connecting, listening, thinking or speaking. |
When the call should know the visitor
A call from the publishable key knows nothing about who is calling. For a signed-in customer, have your server start the call with Start a call, where it can send variables, and hand what comes back to the script:
<button id="talk">Talk to us</button>
<script src="https://YOUR_STUDIO_ADDRESS/embed.js"></script>
<script>
Crackle.init({ button: false });
document.getElementById('talk').onclick = async () => {
// Your route calls POST /v1/calls with your API key and returns what it answers.
const session = await fetch('/api/voice-call', { method: 'POST' }).then((res) => res.json());
Crackle.start({ session }); // { livekitUrl, token, callId }
};
</script>A talk button for your website builds that server route step by step.
What a visitor is told
| Code | When |
|---|---|
AGENT_BUSY | Your workspace is on as many calls as it may run at once. |
AGENT_UNAVAILABLE | The workspace cannot take calls right now. The visitor is not told why. |
EMBED_DAILY_LIMIT_REACHED | The day’s spend limit has been reached. |
RATE_LIMITED | This visitor has started too many calls in the last hour. |
A key that is wrong, a button that is switched off and a site that is not on the list are reported in the browser’s console instead, where you will see them and a visitor will not.