AI Receptionist

Let visitors talk to your AI agent from your app.

Drop-in widget. Load https://webforms.dropcowboy.com/latest/dropcowboy-receptionist.min.js and pass it a site token your server mints. Do not rebuild browser audio capture or the agent session; the widget already does it.

This is the browser widget: a visitor talks to your AI agent from a web page. To have an agent answer your phone line, use the Receptionist API instead; nothing is embedded.

Calling setup comes with the site token. Your page never configures SIP, STUN, or TURN servers; the script does.

When to use this vs REST

Use this widget when a visitor should talk to your AI agent from a web page. To have the agent answer your phone line, use the hosted receptionist API instead: create a knowledge base, create the agent, then assign the line. See Receptionist API reference.

Do not rebuild

  • Browser audio capture and playback.
  • The agent itself. Configure it in AI Agents and pass its id.

Drop-in

  1. On your server, trade your API key for a short-lived site token (the key needs numbers:write):
// Your server. Browser code never sees KEY/SECRET.
app.post('/dropcowboy/token', async function (req, res) {
  const r = await fetch('https://api-v2.dropcowboy.com/phone/public/embed/token', {
    method: 'POST',
    headers: {
      'x-key': process.env.DC_KEY,
      'x-secret': process.env.DC_SECRET,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ site_id: process.env.DC_SITE_ID, scope: ['dialer:webrtc'], ttl_seconds: 900 })
  });
  const body = await r.json();
  if (!r.ok) return res.status(r.status).json(body);
  // The API wraps results in data. Hand the browser just the token.
  res.json({ token: body.data.token, expires_at: body.data.expires_at });
});

Or test the mint with cURL:

# On your server: trade your API key (needs numbers:write) for a site token. 1 hour max.
# site_id is one UUID per app; generate your own in place of the example.
curl -s -X POST https://api-v2.dropcowboy.com/phone/public/embed/token \
  -H "x-key: $KEY" -H "x-secret: $SECRET" \
  -H "Content-Type: application/json" \
  -d '{"site_id":"00000000-0000-4000-8000-000000000000","scope":["dialer:webrtc"],"ttl_seconds":900}'
  1. Load the script, init with the token, and call a method:
<script src="https://webforms.dropcowboy.com/latest/dropcowboy-receptionist.min.js"></script>
<script>
(async function () {
  const minted = await fetch('/dropcowboy/token', { method: 'POST' }).then(function (r) { return r.json(); });
  await DropCowboy.receptionist.init({
    token: minted.token,
    agentId: 'a9c3e8f1-4b2d-4a7c-8e9b-1c2d3e4f5a6b'
  });
  DropCowboy.receptionist.setTheme({ theme: 'LIGHT', primaryColor: '#009efb' });

  DropCowboy.receptionist.addSessionEndedListener(function (event) {
    saveAgentSessionToCrm(event);
  });

  document.getElementById('talk-to-agent').addEventListener('click', function () {
    DropCowboy.receptionist.startSession();
  });
})();
</script>

Pin a version with SRI

/latest/ changes whenever we ship. To pin a release, load the same file from /vX.Y.Z/ and add its Subresource Integrity hash. Each release lists its hashes at https://webforms.dropcowboy.com/vX.Y.Z/building-blocks-manifest.json: take the integrity of the entry whose file is dropcowboy-receptionist.js. It covers dropcowboy-receptionist.min.js as well, because both names serve the same bytes.

<script src="https://webforms.dropcowboy.com/vX.Y.Z/dropcowboy-receptionist.min.js"
        integrity="sha384-INTEGRITY_FROM_MANIFEST"
        crossorigin="anonymous"></script>

Do not put integrity on a /latest/ URL. The file changes under it and the browser will refuse to run it.

JS API / HTML tag

Method What it does
init({ token, agentId }) agentId is the portal AI agent UUID.
setTheme({ theme, primaryColor }) LIGHT/DARK plus accent.
startSession(options) Starts the visitor session with that agent.
addSessionEndedListener(fn) -> stop Fires when the session hangs up.
addErrorListener(fn) -> stop Fires on mint or session errors.
close() Ends the session.

Auth and scopes

Site token. Your API secret stays on your server.

Site token scope: dialer:webrtc. Mint it with POST https://api-v2.dropcowboy.com/phone/public/embed/token using an API key with numbers:write. Tokens last up to one hour.

Limits

  • Site tokens last at most one hour (the default). Mint a fresh one per page load or session; do not cache one across users.
  • Calls and texts need a connected carrier (BYOC) and prepaid balance. Without them the mint returns 402 or 403 for telephony scopes.
  • Sessions bill as AI agent minutes.
  • Calls belong to the Drop Cowboy® user who created the API key that minted the site token. They need no dialer seat, and any number of your users can be on calls at once with tokens from the same key. A token minted from a key with no creator is refused with 403 acting_user_required.
  • To tell your users apart, pass your own user id as sub when you mint the token. The contact.call.* webhooks for the call carry it back as external_user_id, along with site_id.

Routes are on https://api-v2.dropcowboy.com.

Full reference: Embed site tokens, AI agents API.

Copy the code

The snippets on this page. Your server keeps the API key pair (KEY and SECRET for curl, DC_KEY and DC_SECRET for Node); the browser only sees the short-lived site token.

Load the AI Receptionist
<script src="https://webforms.dropcowboy.com/latest/dropcowboy-receptionist.min.js"></script>
<script>
(async function () {
  const minted = await fetch('/dropcowboy/token', { method: 'POST' }).then(function (r) { return r.json(); });
  await DropCowboy.receptionist.init({
    token: minted.token,
    agentId: 'a9c3e8f1-4b2d-4a7c-8e9b-1c2d3e4f5a6b'
  });
  DropCowboy.receptionist.setTheme({ theme: 'LIGHT', primaryColor: '#009efb' });

  DropCowboy.receptionist.addSessionEndedListener(function (event) {
    saveAgentSessionToCrm(event);
  });

  document.getElementById('talk-to-agent').addEventListener('click', function () {
    DropCowboy.receptionist.startSession();
  });
})();
</script>
Mint a site token (your server)
// Your server. Browser code never sees KEY/SECRET.
app.post('/dropcowboy/token', async function (req, res) {
  const r = await fetch('https://api-v2.dropcowboy.com/phone/public/embed/token', {
    method: 'POST',
    headers: {
      'x-key': process.env.DC_KEY,
      'x-secret': process.env.DC_SECRET,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ site_id: process.env.DC_SITE_ID, scope: ['dialer:webrtc'], ttl_seconds: 900 })
  });
  const body = await r.json();
  if (!r.ok) return res.status(r.status).json(body);
  // The API wraps results in data. Hand the browser just the token.
  res.json({ token: body.data.token, expires_at: body.data.expires_at });
});
Mint a site token (curl)
# On your server: trade your API key (needs numbers:write) for a site token. 1 hour max.
# site_id is one UUID per app; generate your own in place of the example.
curl -s -X POST https://api-v2.dropcowboy.com/phone/public/embed/token \
  -H "x-key: $KEY" -H "x-secret: $SECRET" \
  -H "Content-Type: application/json" \
  -d '{"site_id":"00000000-0000-4000-8000-000000000000","scope":["dialer:webrtc"],"ttl_seconds":900}'
Load the AI Receptionist
<script src="https://webforms.dropcowboy.com/vX.Y.Z/dropcowboy-receptionist.min.js"
        integrity="sha384-INTEGRITY_FROM_MANIFEST"
        crossorigin="anonymous"></script>