Phone Hub (Inbound)

Pick a number, set hours, and route inbound calls.

Drop-in widget. Load https://webforms.dropcowboy.com/latest/dropcowboy-phone-hub.min.js and pass it a site token your server mints. Do not rebuild number search, business hours, or call routing forms; 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 Phone Hub to let a user pick a number, set hours, and route inbound calls inside your app. Use REST to rent numbers or list lines from a backend.

Do not rebuild

  • Number search and purchase.
  • Business hours and inbound routing forms.

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: ['phone:hub'], 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":["phone:hub"],"ttl_seconds":900}'
  1. Load the script, init with the token, and call a method:
<script src="https://webforms.dropcowboy.com/latest/dropcowboy-phone-hub.min.js"></script>
<div id="phone-hub-slot"></div>
<script>
(async function () {
  const minted = await fetch('/dropcowboy/token', { method: 'POST' }).then(function (r) { return r.json(); });
  await DropCowboy.phoneHub.init({ token: minted.token });
  DropCowboy.phoneHub.setTheme({ theme: 'LIGHT', primaryColor: '#009efb' });
  DropCowboy.phoneHub.addErrorListener(function (event) {
    console.error(event);
  });
  document.getElementById('rent-number').addEventListener('click', function () {
    DropCowboy.phoneHub.openNumberPicker({ container: '#phone-hub-slot' });
  });
})();
</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-phone-hub.js. It covers dropcowboy-phone-hub.min.js as well, because both names serve the same bytes.

<script src="https://webforms.dropcowboy.com/vX.Y.Z/dropcowboy-phone-hub.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, getToken }) Requires a site token with phone:hub. Optional getToken returns a fresh site token (or { token, expires_at }) from your server; it runs before the token expires and once after a 401.
setToken(token) Swaps in a site token you minted yourself. The next request uses it.
window event 'dc-session-expired' Fires when the token expired and could not be refreshed. Mint a new one and call setToken().
setTheme({ theme, primaryColor }) LIGHT/DARK plus accent.
openHub({ container }) Opens the full inbound hub (numbers, hours, routes). The element shares this session's token manager and retries once after a 401.
openNumberPicker({ container, areaCode, lineId }) Opens the DID search and rent flow. With lineId the rented number is assigned to that line.
<dc-phone-hub> / <dc-number-picker> .tokenManager Set this property to a token manager you already have (for example from another block's getTokenManager()) to drive a tag you placed yourself. The tag borrows it and never closes it. A plain token property still works.
addErrorListener(fn) -> stop Fires { code, message, reason } when a hub or picker opened with openHub() or openNumberPicker() fails to load, search, rent, or assign (dc-error). message is readable as-is. Calls you make yourself, such as rentNumber(), reject instead.
close() Unmounts the hub.

HTML tag:

<dc-phone-hub></dc-phone-hub>

Auth and scopes

Site token with the phone hub scope. Your API secret stays on your server.

Site token scope: phone:hub. 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.
  • E911 and caller name (CNAM) are set in Drop Cowboy®, not in the widget.

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

  • POST /phone/public/numbers/rent - rent a number
  • GET /phone/public/lines - list your numbers
  • POST /phone/public/embed/token - mint the site token

Full reference: Embed site tokens.

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 Phone Hub
<script src="https://webforms.dropcowboy.com/latest/dropcowboy-phone-hub.min.js"></script>
<div id="phone-hub-slot"></div>
<script>
(async function () {
  const minted = await fetch('/dropcowboy/token', { method: 'POST' }).then(function (r) { return r.json(); });
  await DropCowboy.phoneHub.init({ token: minted.token });
  DropCowboy.phoneHub.setTheme({ theme: 'LIGHT', primaryColor: '#009efb' });
  DropCowboy.phoneHub.addErrorListener(function (event) {
    console.error(event);
  });
  document.getElementById('rent-number').addEventListener('click', function () {
    DropCowboy.phoneHub.openNumberPicker({ container: '#phone-hub-slot' });
  });
})();
</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: ['phone:hub'], 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":["phone:hub"],"ttl_seconds":900}'
Load the Phone Hub
<script src="https://webforms.dropcowboy.com/vX.Y.Z/dropcowboy-phone-hub.min.js"
        integrity="sha384-INTEGRITY_FROM_MANIFEST"
        crossorigin="anonymous"></script>
HTML tag
<dc-phone-hub></dc-phone-hub>