# Campaign Monitor

- Tool: Bubble
- Difficulty: Beginner
- Time required: 30–45 minutes
- Last updated: July 2026

## TL;DR

Connect Bubble to Campaign Monitor using the API Connector with Basic Auth — your API key as the username and the literal string 'x' as the password, both via the 'Username & Password' authentication type. Every Campaign Monitor endpoint requires a '.json' extension. The primary use case is an agency dashboard that lets clients view their subscriber lists and campaign open/click rates without accessing Campaign Monitor directly.

## Two Gotchas That Break Every First Campaign Monitor Integration in Bubble

Campaign Monitor has two integration quirks that cause almost every first-time setup to fail, and neither of them is obvious from the API documentation.

The first is the authentication scheme. Campaign Monitor uses Basic Auth, but not the way most APIs do. Instead of passing an API key in the Password field (with a placeholder username), Campaign Monitor expects the API key in the Username field and literally any string in the Password field — the documentation recommends 'x' because it's short and unambiguous. If you try Bearer token authentication, or if you accidentally put the key in the Password field, you get a 401 Unauthorized with no helpful error detail about which field is wrong. In Bubble's API Connector, you select 'Username & Password' as the authentication type and enter the API key as the Username. Bubble handles the base64 encoding automatically.

The second gotcha is the '.json' extension requirement. Every single Campaign Monitor API endpoint must end with '.json' in the URL path. This is not a query parameter or an Accept header — it is literally part of the path: '/clients.json', '/clients/{clientId}/campaigns.json', '/campaigns/{campaignId}/summary.json'. If you omit the extension, Campaign Monitor returns a 404 even for a path that would otherwise be correct. This is unusual enough that it trips up experienced developers who assume a standard REST JSON API.

Once you have these two things right, the Campaign Monitor API is clean and straightforward. Responses are well-structured JSON with meaningful field names. The multi-client account hierarchy — clients containing subscriber lists containing campaigns — maps naturally to a Bubble data model.

The primary Bubble use case for Campaign Monitor is agency dashboards. An agency builds a Bubble app where each client can log in and see their Campaign Monitor subscriber counts, recent campaign performance, and list growth — without the agency sharing Campaign Monitor login credentials. The API Connector pulls the data server-side, privacy rules restrict what each client can see, and the Repeating Group displays the results. This is genuinely useful work that Bubble handles well.

## Before you start

- A Bubble account on any plan — the API Connector plugin works on all Bubble plans including Free
- A Campaign Monitor account on any paid plan (Basic $9/mo or above) — API access is included on all paid plans
- Your Campaign Monitor API key — log in to Campaign Monitor → Account Settings → API Keys → Generate API Key
- Your Campaign Monitor Client ID(s) — found in the Campaign Monitor URL when viewing a client account: /clients/{ClientID}
- The ID of the subscriber list you want to manage — found in Campaign Monitor under the list's Settings page

## Step-by-step guide

### 1. Gather Your Campaign Monitor API Key and Client IDs

Before opening Bubble, collect the credentials you need from Campaign Monitor. These are small but easy to get wrong, so gather them carefully.

Log in to Campaign Monitor and click your account name in the top right, then go to Account Settings → API Keys. If no key exists, click 'Generate API key'. Copy the full key — it looks like a long alphanumeric string. This key gives access to all clients in your account, so store it securely.

Next, locate your Campaign Monitor Client ID. This is not shown on a dedicated settings page — you find it in the URL while viewing a client account. Click on any client in your Campaign Monitor account. Look at the browser URL bar — you'll see a path like `/clients/abc123def456/`. The alphanumeric string between '/clients/' and the trailing slash is the Client ID. Write it down.

If you're building an agency dashboard with multiple clients, you can fetch all Client IDs dynamically via the API later (GET /clients.json) — but for initial setup, having at least one Client ID lets you test calls during the Initialize step.

Also note: Campaign Monitor's pagination for subscriber lists uses 1-indexed page numbers (page=1 is the first page) with a maximum page size of 1000. This is important when fetching large subscriber lists.

**Expected result:** You have your Campaign Monitor API key and at least one Client ID. You know which subscriber list ID you want to work with. You're ready to configure Bubble.

### 2. Install the API Connector and Configure Campaign Monitor Authentication

Open your Bubble editor and go to Plugins → Add plugins. Search for 'API Connector' in the plugins marketplace and install the one by Bubble (free). This plugin is the core of your integration — it runs all HTTP calls from Bubble's servers, not the browser.

In the API Connector panel, click 'Add another API'. Name it 'Campaign Monitor'. In the Base URL field, enter: `https://api.createsend.com/api/v3.3`

Do not add a trailing slash. This base URL is the same for all Campaign Monitor endpoints regardless of which client or account you're working with.

For Authentication, click the dropdown and select 'Username & Password'. This is Bubble's name for HTTP Basic Auth. Two fields appear:

- Username: enter your Campaign Monitor API key (the long alphanumeric string from Account Settings)
- Password: enter the literal string `x` — just the letter x, nothing else
- Check the 'Private' checkbox next to the Username field — this keeps your API key server-side and out of the browser

The password 'x' is not a placeholder that you replace with something else. Campaign Monitor does not validate the password field at all — it only checks the API key in the username position. Using any other string works too, but 'x' is the convention from Campaign Monitor's own documentation.

Do NOT select 'Bearer token' or 'Private key in header'. Either will cause 401 Unauthorized errors even with a valid API key.

Click 'Save'.

```
{
  "base_url": "https://api.createsend.com/api/v3.3",
  "authentication": {
    "type": "Username & Password (HTTP Basic Auth)",
    "username": "<private: your_campaign_monitor_api_key>",
    "password": "x"
  },
  "note": "Campaign Monitor ignores the password value. API key must be the username."
}
```

**Expected result:** Campaign Monitor appears in your API Connector list with the base URL and Username & Password authentication configured. The Username (API key) is marked as Private. Ready to add API calls.

### 3. Create the Get Clients Call and Initialize It

Add your first API call to pull the list of Campaign Monitor clients. This call is the foundation of an agency dashboard — it populates the client selector Dropdown.

Click 'Add a new call' under the Campaign Monitor API. Name it 'Get Clients'. Set the Method to GET. Set the path to `/clients.json`.

This is where the '.json' extension requirement becomes real. The path is not `/clients` — it is `/clients.json`. The extension is a literal part of every Campaign Monitor URL path, not a content negotiation header. If you enter `/clients` without the extension, the API returns a 404 even though the authentication is correct and the path would otherwise be valid. Set 'Use as' to Data.

Click 'Initialize call'. You do not need any parameters for this call — it returns all clients associated with your account-level API key. If the initialization succeeds, you'll see a JSON array like:

```json
[
  { "ClientID": "abc123", "Name": "Acme Corp" },
  { "ClientID": "def456", "Name": "BlueSky Agency" }
]
```

Bubble detects the array structure and maps ClientID and Name as available fields. If you have only one client, you still get an array with one item — Bubble handles this correctly.

After initialization, go to your Bubble page in the Design tab. Add a Dropdown element. Set its 'Choices' data source to 'Get Clients (API call)' and map the label to 'Name' and the value to 'ClientID'. Now you have a dynamic client selector powered by live Campaign Monitor data.

```
{
  "method": "GET",
  "path": "/clients.json",
  "note": "The .json extension is required. /clients (without .json) returns 404.",
  "example_response": [
    { "ClientID": "abc123def", "Name": "Acme Corp" },
    { "ClientID": "xyz789", "Name": "BlueSky Agency" }
  ]
}
```

**Expected result:** The Get Clients call initializes successfully and returns a list of client objects with ClientID and Name fields. Bubble detects the array structure. The call shows as green in the API Connector.

### 4. Create Calls for Campaign Lists and Per-Campaign Stats

Now add the calls that power the core of the dashboard: fetching campaigns for a selected client, and fetching summary stats for each campaign.

Add a new call. Name it 'Get Client Campaigns'. Method = GET. Path = `/clients/<dynamic: client_id>/campaigns.json`. The dynamic parameter `client_id` will be populated from the Dropdown selection when this call is used in a workflow or as a data source. Use as = Data.

Initialize this call using one of your real Client IDs. The response includes an array of campaign objects, each with fields like CampaignID, Name, SentDate, TotalRecipients, and the campaign statistics.

Add another call. Name it 'Get Campaign Summary'. Method = GET. Path = `/campaigns/<dynamic: campaign_id>/summary.json`. Use as = Data. Initialize with a real campaign ID from your account. The summary response includes:

- `Opens.UniqueOpens` — unique opens count
- `Clicks.UniqueClicks` — unique clicks count
- `Recipients` — total recipients
- `Unsubscribes` — unsubscribe count

Bubble maps these nested fields using dot notation. To display the open rate as a percentage, calculate it in a Bubble formula: `Opens.UniqueOpens / Recipients * 100`.

For agencies with large accounts (many campaigns per client), avoid calling 'Get Campaign Summary' for every campaign on every page load. Instead, build a caching layer: create a Bubble data type called 'CampaignStat' with fields for CampaignID (text), OpenRate (number), ClickRate (number), Recipients (number), and LastRefreshed (date). A 'Refresh Stats' button in the UI triggers a workflow that calls Get Campaign Summary for each campaign and saves the results. The Repeating Group displays from the cached CampaignStat records rather than making live API calls — important at Campaign Monitor's 10 requests/second rate limit.

```
{
  "get_campaigns": {
    "method": "GET",
    "path": "/clients/<dynamic: client_id>/campaigns.json",
    "example_response": [
      {
        "CampaignID": "camp_abc123",
        "Name": "July Newsletter",
        "SentDate": "2026-07-01 10:00:00",
        "TotalRecipients": 3420
      }
    ]
  },
  "get_campaign_summary": {
    "method": "GET",
    "path": "/campaigns/<dynamic: campaign_id>/summary.json",
    "example_response": {
      "Recipients": 3420,
      "Opens": { "UniqueOpens": 1026 },
      "Clicks": { "UniqueClicks": 287 },
      "Unsubscribes": 12
    }
  }
}
```

**Expected result:** Both calls initialize successfully. Get Client Campaigns returns an array of campaign objects with CampaignID and Name. Get Campaign Summary returns open and click counts for the specified campaign. Bubble detects all nested fields.

### 5. Build the Dashboard UI and Wire Up Subscriber Management

With all API calls configured, build the Bubble UI that brings the dashboard together.

On your Dashboard page, add:
1. A Dropdown element with data source = Get Clients API call. Label field = Name, value field = ClientID.
2. A Repeating Group below the Dropdown. Set its data type to 'CampaignStat' (your cache data type from the previous step). Add a filter: CampaignStat's ClientID = Dropdown's value.
3. Inside the Repeating Group, add text elements for: Campaign Name, Sent Date, Open Rate (calculate from cached values: Current cell's CampaignStat's OpenRate formatted with 2 decimal places + '%'), Click Rate (same pattern).

Add a 'Refresh' button. Its click workflow:
- Step 1: Call Get Client Campaigns with client_id = Dropdown's value
- Step 2: For each campaign in the result list, call Get Campaign Summary (use Bubble's 'Make changes to a list' or a recursive workflow if the list is long)
- Step 3: Create or update CampaignStat records with the fresh data and set LastRefreshed = Current date/time

For subscriber management, add another call: 'Add Subscriber'. Method = POST. Path = `/subscribers/<dynamic: list_id>.json`. Body type = JSON. The request body:

```json
{
  "EmailAddress": "<dynamic: email>",
  "Name": "<dynamic: name>",
  "CustomFields": [],
  "Resubscribe": true,
  "ConsentToTrack": "Yes"
}
```

The `Resubscribe: true` field re-subscribes users who previously unsubscribed — similar to Mailchimp's PUT upsert. `ConsentToTrack: "Yes"` is required for GDPR compliance in Campaign Monitor — it must be one of 'Yes', 'No', or 'Unchanged'.

RapidDev's team has built multi-client Campaign Monitor dashboards for several agencies in Bubble — if you need custom role-based access logic (clients see only their own data, agency admin sees all), book a free scoping call at rapidevelopers.com/contact.

```
{
  "method": "POST",
  "path": "/subscribers/<dynamic: list_id>.json",
  "body": {
    "EmailAddress": "<dynamic: email>",
    "Name": "<dynamic: name>",
    "CustomFields": [],
    "Resubscribe": true,
    "ConsentToTrack": "Yes"
  },
  "note": "ConsentToTrack must be 'Yes', 'No', or 'Unchanged' — required field."
}
```

**Expected result:** The dashboard displays clients in the Dropdown, campaigns in the Repeating Group with open and click rates from the cache, and the Refresh button updates the cached stats on demand. The Add Subscriber call successfully adds new contacts to the designated list.

### 6. Add Privacy Rules and Restrict Data Access by Role

Campaign Monitor subscription and campaign performance data represents paid business intelligence that should not be readable by all Bubble app users. Add privacy rules before publishing.

Go to the Data tab in Bubble. Click on your CampaignStat data type. Select 'Set privacy rules'.

Add a rule: 'This Thing is visible to' → 'Current User's Role is Admin'. For an agency app where individual clients can log in and see their own data, add a second rule: 'This Thing is visible to' → 'Current User's ClientID = This CampaignStat's ClientID' (assuming you store a ClientID reference on the User record during signup).

Without privacy rules, any logged-in Bubble user (or even public visitors if the data type is exposed via Bubble's Data API) can read campaign stats from the database. This is particularly important for multi-tenant agency dashboards where Client A should never see Client B's numbers.

Also set privacy rules on any Bubble data type that stores raw subscriber emails pulled from Campaign Monitor subscriber lists — individual email addresses are personal data under GDPR and should be restricted to admin-only access. Aggregated counts (TotalActiveSubscribers) are generally safe to show client-facing without individual-level exposure.

Finally, verify your Campaign Monitor API key is set to read-only in Campaign Monitor's account settings if you don't need write access (subscriber management). A read-only key limits the blast radius if the key were ever compromised.

**Expected result:** Privacy rules are set on CampaignStat and any data types storing subscriber emails. Non-admin users cannot read other clients' campaign data. Admin users can see all records. The Bubble app is ready for multi-tenant agency use.

## Best practices

- Always put the Campaign Monitor API key in the Username field of Bubble's Username & Password authentication — not the Password field. Mark it Private. This is the single most important configuration detail.
- Include the '.json' extension on every Campaign Monitor endpoint path. Build a habit of checking this first whenever you create a new API call or encounter a 404.
- Cache campaign summary stats in a Bubble data type (CampaignStat) with a LastRefreshed timestamp. Drive the dashboard from the cache, not from live API calls — avoids rate limit issues and reduces Workload Unit consumption.
- Apply privacy rules to every Bubble data type that stores Campaign Monitor data, especially subscriber email addresses and campaign performance metrics. Multi-tenant agency dashboards require strict per-client data isolation.
- Store the Campaign Monitor Client IDs and list IDs in a Bubble configuration data type (e.g., AppConfig or ClientConfig) rather than hardcoding them in API call parameters — makes it easy to add new clients without modifying the workflow.
- Add the ConsentToTrack field to all subscriber add and update calls to maintain GDPR compliance. Make this a captured checkbox on your Bubble signup forms rather than hardcoding 'Yes'.
- Monitor Workload Unit consumption in Bubble's Logs tab when running the campaign summary enrichment workflow — each API call consumes WU. For large accounts, run enrichment during off-peak hours via a scheduled Backend Workflow (requires paid Bubble plan).
- Restrict the Campaign Monitor API key to read-only in Campaign Monitor account settings if your Bubble app only needs to display data. A read-only key limits potential damage if ever exposed.

## Use cases

### Agency Client Dashboard

Build a multi-client email performance dashboard in Bubble: clients log in, select their brand from a Dropdown populated from the Campaign Monitor client list, and see their subscriber count, most recent campaign open rate, and click rate. The agency admin can see all clients; individual clients see only their own data. No Campaign Monitor credentials are shared.

Prompt example:

```
When page is loaded: Step 1 - Call 'Get Clients' API call and load results into Dropdown element. When Dropdown value changes: Step 2 - Call 'Get Client Campaigns' with selected client_id. Step 3 - Load campaign list into Repeating Group. For each campaign row: show campaign name, sent date, open rate, click rate from cached DomainMetrics data type.
```

### Subscriber List Growth Tracker

Pull subscriber list stats for a client's lists and store them daily in a Bubble data type with a timestamp. Display a simple count + trend (this week vs. last week) on a Bubble admin page. Clients see list growth without needing a Campaign Monitor login, and the agency can set alerts when a list drops below a threshold.

Prompt example:

```
Scheduled Backend Workflow (daily): Step 1 - Call 'Get Subscriber Lists' for each client_id. Step 2 - Save TotalActiveSubscribers, TotalUnsubscribes, and timestamp to ListSnapshot data type. On dashboard page: bind Repeating Group to ListSnapshot filtered by today's date.
```

### New Subscriber Sync from Bubble Forms

When a user signs up through a Bubble landing page, automatically add them to the correct Campaign Monitor subscriber list via POST /subscribers/{listId}.json. Include first name, last name, and any custom fields defined on the list. Display a success state on the form after the subscriber is added.

Prompt example:

```
On Subscribe button click: Step 1 - Call 'Add Subscriber' API with email = Email input's value, first_name = First Name input's value, last_name = Last Name input's value, listId = static Campaign Monitor list ID. Step 2 - Only when result is success: show 'You're subscribed!' text group. Step 3 - Clear input fields.
```

## Troubleshooting

### Authentication returns 401 Unauthorized even though the API key looks correct

Cause: Campaign Monitor's Basic Auth requires the API key in the Username field, not the Password field. Using Bearer token auth or putting the key in the Password field causes a 401 with no detail about which field is wrong.

Solution: In Bubble's API Connector, select 'Username & Password' as the authentication type. Put the Campaign Monitor API key in the Username field (mark it Private). Put the literal string 'x' in the Password field. Do not use Bearer token authentication — Campaign Monitor v3.3 does not accept Bearer tokens.

### API calls return 404 Not Found even for valid endpoint paths

Cause: Campaign Monitor requires a '.json' extension at the end of every URL path. Paths like '/clients' or '/clients/{clientId}/campaigns' return 404 — the correct paths are '/clients.json' and '/clients/{clientId}/campaigns.json'.

Solution: Add '.json' at the end of every Campaign Monitor endpoint path in your API Connector call configuration. Check all existing calls and update any that are missing the extension. This applies to every endpoint including GET, POST, PUT, and DELETE paths.

### Initialize call fails or shows an empty response with no detected fields

Cause: Bubble's Initialize call requires a real successful HTTP 200 response to detect response fields. If authentication is wrong, the path is missing .json, or you're using a Client ID that doesn't belong to your account, the initialization fails and Bubble cannot map any fields.

Solution: Before clicking Initialize, verify the endpoint works by opening it in your browser (for GET calls): navigate to https://api.createsend.com/api/v3.3/clients.json with your API key as HTTP Basic Auth username. If the browser returns JSON, your credentials and path are correct. Then use the Initialize call in Bubble with the same values. For POST/PUT calls, use a test email address and real list ID during initialization.

### Get Campaign Summary fails for accounts with many campaigns and hits rate limits

Cause: Fetching summary stats for each campaign requires one API call per campaign. For accounts with 100+ campaigns, calling all of them sequentially in a Bubble workflow exceeds the 10 requests/second rate limit, resulting in 429 Too Many Requests errors.

Solution: Add caching: create a CampaignStat Bubble data type and save results after the first fetch. Only re-fetch stats older than a set threshold (e.g., 24 hours) using an 'Only when' condition on the workflow. Add a 200ms 'Pause before next step' between API calls in the enrichment workflow to stay under the rate limit. Never call the summary endpoint on every page load — always read from the Bubble database cache.

### POST /subscribers returns an error saying ConsentToTrack is invalid

Cause: The ConsentToTrack field is required by Campaign Monitor for all subscriber add requests. It must be exactly one of the strings 'Yes', 'No', or 'Unchanged' (case-sensitive). Omitting this field or using a boolean (true/false) causes a 400 error.

Solution: Add 'ConsentToTrack': 'Yes' to the request body of your Add Subscriber API call in the API Connector. If you need to handle explicit consent from a form checkbox, use a dynamic parameter that maps to 'Yes' when the checkbox is checked and 'Unchanged' when unchecked.

## Frequently asked questions

### Why does Campaign Monitor use 'x' as the password in Basic Auth?

Campaign Monitor's Basic Auth implementation only validates the username field (which holds your API key). The password is completely ignored — Campaign Monitor accepts any string, including an empty string, though their documentation recommends 'x' as a convention. This is a design choice from Campaign Monitor, not a security issue in Bubble. The important thing is that the API key is in the Username field, marked as Private in Bubble's API Connector.

### Do I need a paid Bubble plan to connect to Campaign Monitor?

The basic integration — calling Campaign Monitor APIs from button click workflows and displaying data in Repeating Groups — works on Bubble's free plan. You only need a paid Bubble plan if you want to use Backend Workflows for scheduled data refresh (e.g., automatically updating campaign stats once per day). API Connector calls triggered by user actions work on all plans.

### Do I need a paid Campaign Monitor plan for API access?

Yes. Campaign Monitor API access is available on all paid plans (Basic $9/mo and above). There is no API access on Campaign Monitor's free trial. The most common plan for agency dashboards is the Basic plan, which includes full API access and the multi-client account structure.

### How do I show subscriber data for a specific list without exposing individual email addresses?

Use the GET /clients/{clientId}/lists.json endpoint to fetch list-level aggregate stats (TotalActiveSubscribers, TotalUnsubscribes, TotalBounces) rather than pulling individual subscriber records. Display these aggregate counts in your Bubble UI. Only fetch /lists/{listId}/active.json (which returns individual subscriber emails) if you specifically need to display or export individual records — and if you do, apply strict privacy rules to the Bubble data type that stores them.

### How do I handle pagination when fetching large subscriber lists?

Campaign Monitor's subscriber list endpoint (/lists/{listId}/active.json) accepts URL parameters 'page' (1-indexed, starting at 1) and 'pagesize' (maximum 1000). For large lists, create a Bubble workflow that iterates through pages: fetch page 1, save results, check if the returned count equals pagesize (if yes, fetch page 2, and so on). Bubble's recursive workflow pattern or a 'Schedule API workflow on a list' action handles pagination well. Note that Bubble's free plan has limits on workflow scheduling depth.

### Can I build a dashboard for multiple agency clients in one Bubble app?

Yes, and this is Campaign Monitor's strength. Use the GET /clients.json call to populate a client selector (Dropdown), then use the selected ClientID as a dynamic parameter in all subsequent calls (campaigns, lists, stats). Store a ClientID field on Bubble User records to associate each client's login with their Campaign Monitor account. Privacy rules on Bubble data types ensure Client A cannot see Client B's data. This multi-tenant pattern is exactly what Campaign Monitor's account hierarchy is designed for.

---

Source: https://www.rapidevelopers.com/bubble-integrations/campaign-monitor
© RapidDev — https://www.rapidevelopers.com/bubble-integrations/campaign-monitor
