The Dock (one widget)

One widget for a CRM: click a contact, dial, text, and open inbox.

Drop-in widget. Load https://webforms.dropcowboy.com/latest/dropcowboy-dock.min.js and pass it a site token your server mints. Do not rebuild WebRTC calling, the inbox, or the contact panel; the widget already does it.

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 the Dock when agents work inside your CRM and need to dial, text, and see the inbox without leaving it. Use REST when no person is at a screen, for example sending a follow-up text from a job.

Do not rebuild

  • A softphone, call controls, or audio device handling.
  • The unified inbox or contact side panel.
  • Caller ID selection. Local presence picks the number.

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', 'contacts'], 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.
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":"YOUR_SITE_ID","scope":["dialer:webrtc","contacts"],"ttl_seconds":900}'
  1. Load the script, init with the token, and call a method:
<script src="https://webforms.dropcowboy.com/latest/dropcowboy-dock.min.js"></script>
<script>
(async function () {
  const minted = await fetch('/dropcowboy/token', { method: 'POST' }).then(function (r) { return r.json(); });
  await DropCowboy.dock.init({
    token: minted.token,
    mode: 'floating',
    panes: ['dialer', 'inbox', 'contacts']
  });
  DropCowboy.dock.setTheme({ theme: 'LIGHT', primaryColor: '#009efb' });

  DropCowboy.dock.addPaneChangeListener(function (event) {
    console.log('dock pane', event.pane);
  });

  document.getElementById('contacts').addEventListener('click', function (e) {
    var row = e.target.closest('[data-contact-id]');
    if (!row) return;
    DropCowboy.dock.dial({
      contact: {
        id: row.getAttribute('data-contact-id'),
        name: row.getAttribute('data-name'),
        phone: row.getAttribute('data-phone')
      }
    });
  });
})();
</script>

JS API / HTML tag

Method What it does
init({ token, mode, dockRole, partnerName, panes }) Mounts the dock. mode is 'floating', 'pinned', or 'fill'. panes is dialer, messages, inbox, contacts, pipeline, campaigns.
setTheme({ theme, primaryColor }) Applies LIGHT/DARK and your accent color to every pane.
dial({ contact, container }) Opens the dialer pane and preloads the contact. contact needs id, name, phone.
open(pane, { contact, container }) Opens a specific pane. Same contact shape as dial().
setMode('floating' | 'pinned' | 'fill') Changes layout at runtime.
setPanes([pane, ...]) Shows or hides nav items. If the active pane is removed, the dock switches to the first remaining pane.
addPaneChangeListener(fn) -> stop Fires { pane } on nav clicks and open()/dial()/setPanes().
addDockToggleListener(fn) -> stop Fires { open } when the dock expands or minimizes.
close() Minimizes the dock and drops the session token.

HTML tag:

<dc-dock mode="floating" panes="dialer,inbox,contacts" open></dc-dock>

Auth and scopes

Site token. Your API secret stays on your server.

Site token scope: dialer:webrtc contacts. 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.

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

  • POST /phone/public/embed/token - mint the site token
  • POST /sms - send a text from your server
  • GET /contact/public/contacts - look up contacts

Full reference: Embed site tokens, Texts API, Contacts 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 Dock
<script src="https://webforms.dropcowboy.com/latest/dropcowboy-dock.min.js"></script>
<script>
(async function () {
  const minted = await fetch('/dropcowboy/token', { method: 'POST' }).then(function (r) { return r.json(); });
  await DropCowboy.dock.init({
    token: minted.token,
    mode: 'floating',
    panes: ['dialer', 'inbox', 'contacts']
  });
  DropCowboy.dock.setTheme({ theme: 'LIGHT', primaryColor: '#009efb' });

  DropCowboy.dock.addPaneChangeListener(function (event) {
    console.log('dock pane', event.pane);
  });

  document.getElementById('contacts').addEventListener('click', function (e) {
    var row = e.target.closest('[data-contact-id]');
    if (!row) return;
    DropCowboy.dock.dial({
      contact: {
        id: row.getAttribute('data-contact-id'),
        name: row.getAttribute('data-name'),
        phone: row.getAttribute('data-phone')
      }
    });
  });
})();
</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', 'contacts'], 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.
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":"YOUR_SITE_ID","scope":["dialer:webrtc","contacts"],"ttl_seconds":900}'
HTML tag
<dc-dock mode="floating" panes="dialer,inbox,contacts" open></dc-dock>