Campaign Builders

Show campaign status and progress inside your app.

Drop-in widget. Load https://webforms.dropcowboy.com/latest/dropcowboy-campaigns.min.js and pass it a site token your server mints. Do not rebuild campaign status and progress displays; the widget already does it.

The widget starts, pauses, and shows campaigns. Create them from your server over REST: email, AI calls, and every other type. See From your server below.

When to use this vs REST

Use the widget to show campaign status in your app. A site token can only read campaigns, so the hub hides Start, Pause, and New. To start or pause from your app, call the REST routes from your server with an API key that has campaigns:send. Use REST to create campaigns from code; POST /campaign/public/campaigns takes type rvm, sms, email, voice_broadcast, or ai_broadcast.

Do not rebuild

  • Campaign status and progress displays.

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: ['campaigns'], 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":["campaigns"],"ttl_seconds":900}'
  1. Load the script, init with the token, and call a method:
<script src="https://webforms.dropcowboy.com/latest/dropcowboy-campaigns.min.js"></script>
<script>
(async function () {
  const minted = await fetch('/dropcowboy/token', { method: 'POST' }).then(function (r) { return r.json(); });
  await DropCowboy.campaigns.init({ token: minted.token });
  const status = await DropCowboy.campaigns.getStatus('3f8a2c14-9b07-4e63-8d51-a6c0e2f49b1d');
  document.getElementById('campaign-state').textContent = status.status;

  // Pausing needs campaigns:send, which a site token does not carry. Your server pauses with its API key.
  document.getElementById('pause').addEventListener('click', function () {
    fetch('/my-server/campaigns/3f8a2c14-9b07-4e63-8d51-a6c0e2f49b1d/pause', { method: 'POST' });
  });
})();
</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-campaigns.js. It covers dropcowboy-campaigns.min.js as well, because both names serve the same bytes.

<script src="https://webforms.dropcowboy.com/vX.Y.Z/dropcowboy-campaigns.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.

From your server

The widget cannot create campaigns. Create them from your server with an API key: campaigns:write to create, campaigns:send to start or pause. Every campaign needs list_ids; look them up with GET /contact/public/lists.

Create an email campaign:

curl -X POST https://api-v2.dropcowboy.com/campaign/public/campaigns \
  -H "x-key: $KEY" -H "x-secret: $SECRET" \
  -H "Content-Type: application/json" \
  -d '{"type":"email","campaign_data":{"name":"Spring newsletter","list_ids":["8f3e1a9c-2d4b-4e7a-9c1f-5b6a7c8d9e0f"],"email_from_mailbox_id":"c5f1a8d3-6e2b-4c9f-b1a7-8d4e2c6f9b35","email_subject":"Spring hours","email_html":"<p>We are open Saturdays this spring.</p>"}}'

Create an AI call campaign:

curl -X POST https://api-v2.dropcowboy.com/campaign/public/campaigns \
  -H "x-key: $KEY" -H "x-secret: $SECRET" \
  -H "Content-Type: application/json" \
  -d '{"type":"ai_broadcast","campaign_data":{"name":"Spring follow-up","list_ids":["8f3e1a9c-2d4b-4e7a-9c1f-5b6a7c8d9e0f"],"phone_line_id":"e2b6f9a3-5c1d-4e8b-a4f7-9c3e1b5d7a28","brand_id":"2c9d4e1f-7a3b-4f6c-8d2e-9b1a5c7e3f40","ai_agent":{"agent_id":"a9c3e8f1-4b2d-4a7c-8e9b-1c2d3e4f5a6b"}}}'

Start a campaign:

curl -X POST https://api-v2.dropcowboy.com/campaign/public/campaigns/3f8a2c14-9b07-4e63-8d51-a6c0e2f49b1d/start \
  -H "x-key: $KEY" -H "x-secret: $SECRET"

JS API / HTML tag

Method What it does
init({ token, getToken }) Site token with the campaigns scope. 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().
getStatus(campaignId) Returns mapped status for one campaign.
list(query) Lists campaigns. Optional status, type, search_term, limit.
getStats(campaignId, query) Returns rollup stats.
open({ container, readonly }) Mounts the campaign hub, loads the list, and selects the first campaign. Start, Pause, and New show only when the token allows them (campaigns:send, campaigns:write); readonly hides them regardless. A list that cannot load shows the reason in the hub and fires dc-error and the error listener.
start(campaignId) Starts a paused/draft campaign. Needs campaigns:send, so with a site token it is refused with 403. Confirm spend in the host if you set DropCowboy.confirmSpend.
pause(campaignId) Pauses a running campaign. Needs campaigns:send, so with a site token it is refused with 403.

HTML tag:

<dc-campaign-status campaign-id="3f8a2c14-9b07-4e63-8d51-a6c0e2f49b1d"></dc-campaign-status>

Auth and scopes

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

Site token scope: campaigns. 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.
  • The widget cannot create campaigns. Create them over REST.
  • Each new campaign is scored for compliance risk. Low-risk campaigns are approved automatically; others wait for review (approved is false) before they can send.
  • Starting a campaign is not delivery. Each contact's result arrives on the channel's status webhook.

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

  • POST /campaign/public/campaigns - create (campaigns:write)
  • POST /campaign/public/campaigns/{id}/start - start (campaigns:send)
  • POST /campaign/public/campaigns/{id}/pause - pause (campaigns:send)
  • GET /campaign/public/campaigns - list

Full reference: Embed site tokens, Campaigns API, Webhooks.

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 Campaign Builders
<script src="https://webforms.dropcowboy.com/latest/dropcowboy-campaigns.min.js"></script>
<script>
(async function () {
  const minted = await fetch('/dropcowboy/token', { method: 'POST' }).then(function (r) { return r.json(); });
  await DropCowboy.campaigns.init({ token: minted.token });
  const status = await DropCowboy.campaigns.getStatus('3f8a2c14-9b07-4e63-8d51-a6c0e2f49b1d');
  document.getElementById('campaign-state').textContent = status.status;

  // Pausing needs campaigns:send, which a site token does not carry. Your server pauses with its API key.
  document.getElementById('pause').addEventListener('click', function () {
    fetch('/my-server/campaigns/3f8a2c14-9b07-4e63-8d51-a6c0e2f49b1d/pause', { method: 'POST' });
  });
})();
</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: ['campaigns'], 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":["campaigns"],"ttl_seconds":900}'
Load the Campaign Builders
<script src="https://webforms.dropcowboy.com/vX.Y.Z/dropcowboy-campaigns.min.js"
        integrity="sha384-INTEGRITY_FROM_MANIFEST"
        crossorigin="anonymous"></script>
Create an email campaign
curl -X POST https://api-v2.dropcowboy.com/campaign/public/campaigns \
  -H "x-key: $KEY" -H "x-secret: $SECRET" \
  -H "Content-Type: application/json" \
  -d '{"type":"email","campaign_data":{"name":"Spring newsletter","list_ids":["8f3e1a9c-2d4b-4e7a-9c1f-5b6a7c8d9e0f"],"email_from_mailbox_id":"c5f1a8d3-6e2b-4c9f-b1a7-8d4e2c6f9b35","email_subject":"Spring hours","email_html":"<p>We are open Saturdays this spring.</p>"}}'
Create an AI call campaign
curl -X POST https://api-v2.dropcowboy.com/campaign/public/campaigns \
  -H "x-key: $KEY" -H "x-secret: $SECRET" \
  -H "Content-Type: application/json" \
  -d '{"type":"ai_broadcast","campaign_data":{"name":"Spring follow-up","list_ids":["8f3e1a9c-2d4b-4e7a-9c1f-5b6a7c8d9e0f"],"phone_line_id":"e2b6f9a3-5c1d-4e8b-a4f7-9c3e1b5d7a28","brand_id":"2c9d4e1f-7a3b-4f6c-8d2e-9b1a5c7e3f40","ai_agent":{"agent_id":"a9c3e8f1-4b2d-4a7c-8e9b-1c2d3e4f5a6b"}}}'
Start a campaign
curl -X POST https://api-v2.dropcowboy.com/campaign/public/campaigns/3f8a2c14-9b07-4e63-8d51-a6c0e2f49b1d/start \
  -H "x-key: $KEY" -H "x-secret: $SECRET"
HTML tag
<dc-campaign-status campaign-id="3f8a2c14-9b07-4e63-8d51-a6c0e2f49b1d"></dc-campaign-status>