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

Before </body>
<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-agentstring

The agent’s publishable key.

data-labelstringdefault Talk to us

The words on the button.

data-colorhex colourdefault #111111

The button’s colour. Its text turns black or white to suit.

data-positionenumbottom-right · bottom-left

Which corner it sits in.

data-buttonbooleandefault true

false 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 20

The 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 300

The longest one call from the button may run.

dailySpendLimitnumberdefault 5

The 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.

Your own button
<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>
CallWhat 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.stateidle, 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:

A call your server started
<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

CodeWhen
AGENT_BUSYYour workspace is on as many calls as it may run at once.
AGENT_UNAVAILABLEThe workspace cannot take calls right now. The visitor is not told why.
EMBED_DAILY_LIMIT_REACHEDThe day’s spend limit has been reached.
RATE_LIMITEDThis 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.