# Facebook Ads

- Tool: Bubble
- Difficulty: Intermediate
- Time required: 60–90 minutes
- Last updated: July 2026

## TL;DR

Connect Bubble to Facebook Ads by creating a Meta Business Manager System User with a non-expiring access token, then configuring Bubble's API Connector with the token as a Private parameter. Query campaign metrics via the Meta Insights API and display results in Bubble repeating groups. Optionally, set up a Backend Workflow endpoint to receive Lead Ads webhook events and sync lead data into Bubble's database.

## The Right Way to Connect Bubble to Facebook Ads: System User Tokens, Not Personal OAuth

The most common Facebook Ads integration mistake in Bubble is using a personal user access token — the kind you get from clicking 'Get Token' in the Meta Graph API Explorer. These tokens expire after 60 days of inactivity (or sooner). There is no built-in refresh mechanism in Bubble. One day, without warning, the token silently stops working and your dashboard stops showing data. Founders usually discover this when a client calls asking why the dashboard is blank.

The correct pattern is a Meta Business Manager System User. System Users are service accounts — they don't represent a human, they represent your application. System User tokens can be generated without expiry. This is the token your Bubble API Connector should use.

Once the token is configured, the Meta Insights API is clean to work with. You make a GET request to `/act_{adAccountId}/insights` with a `fields` parameter listing the metrics you want, and Meta returns a JSON array of campaign performance data. Bubble can bind this directly to a Repeating Group, letting your team see clicks, impressions, spend, and conversions without accessing Ads Manager.

The second pattern — Lead Ads webhook sync — is more advanced but extremely valuable. When someone fills out a Meta Lead Form directly in Facebook or Instagram, Meta sends that lead's contact info to a webhook URL within seconds. By pointing Meta's webhook at a Bubble Backend Workflow endpoint, you can write the lead directly to your Bubble database, trigger an email via SendGrid, and start a nurture sequence — all without manual CSV exports from Ads Manager.

## Before you start

- A Bubble account on any plan (API Connector works on all plans; Backend Workflows for Lead Ads webhooks require a paid plan)
- A Meta Business Manager account (business.facebook.com) with admin access to the Facebook Ad Account you want to connect
- A Meta Developer App created at developers.facebook.com — even if you're not building a consumer app, this is required to generate System User tokens
- A System User created in Business Manager → System Users with Analyst or Advertiser role on the target ad account
- A non-expiring System User access token with at minimum 'ads_read' permission scope
- The Ad Account ID from Business Manager (a numeric ID shown as 'Account ID' in Ads Manager — without the act_ prefix, you'll add that in the API call path)

## Step-by-step guide

### 1. Create a Meta Business Manager System User and Generate a Non-Expiring Token

Log in to Meta Business Manager at business.facebook.com. Navigate to Business Settings → Users → System Users. Click 'Add' to create a new System User. Give it a descriptive name like 'Bubble Dashboard Integration'. Set the role to 'Employee' (Analyst access level is sufficient for read-only dashboard use).

After creating the System User, click on it and then click 'Add Assets'. Select 'Ad Accounts' from the asset type list. Find the ad account you want to connect and assign it with 'Analyst' permission (or 'Advertiser' if you plan to make campaign changes later).

Now generate the token. Still in the System User detail view, click 'Generate New Token'. Select your Developer App from the dropdown. Under permissions, check `ads_read`. If you also plan to receive Lead Ads webhook data, also check `leads_retrieval`. Critically — set the token expiration to 'Never'. Click 'Generate Token'.

Copy the token immediately and store it securely. This is a long, opaque string. It does not expire unless you manually revoke it or add/remove permissions from the System User. This is the token your Bubble integration will use permanently.

**Expected result:** You have a System User with Analyst permission on the ad account. You have a non-expiring access token with ads_read scope. You have your numeric Ad Account ID from Business Manager.

### 2. Configure the API Connector with Meta Base URL and Private Token Parameter

Open your Bubble editor and go to Plugins → Add plugins. Search for 'API Connector' and install it (by Bubble, free).

In the API Connector, click 'Add another API'. Name it 'Facebook Ads'. Set the base URL to `https://graph.facebook.com/v19.0`.

In the Authentication section, choose 'None or self-handled'. Now you'll add the access token as a shared parameter rather than a header — Meta supports both authentication methods, but URL parameters are simpler to configure in Bubble's API Connector UI.

Under 'Shared parameters for all calls', add:
- Name: `access_token`
- Value: your System User token (the long string you generated)
- Check the 'Private' checkbox — this ensures the token is not included in any browser-visible network request

Leave the Content-Type at default for GET calls. You'll add `Content-Type: application/json` at the individual call level for any POST calls.

```
{
  "base_url": "https://graph.facebook.com/v19.0",
  "shared_parameters": [
    {
      "name": "access_token",
      "value": "EAAG...yourtoken...",
      "private": true
    }
  ]
}
```

**Expected result:** The 'Facebook Ads' API appears in your API Connector list with the access_token parameter marked Private. No API calls created yet — that's the next step.

### 3. Create the Insights API Call for Campaign Metrics

Click 'Add a new call' under your Facebook Ads API. Name it 'Get Campaign Insights'. Set method to GET. Set path to `/act_<dynamic: ad_account_id>/insights`.

Add the following URL parameters to this call:
- `fields`: `campaign_name,spend,impressions,clicks,ctr,cpm,actions` (this is a comma-separated string of the metrics you want). You can hardcode this value or make it dynamic.
- `date_preset`: `last_30d` (or make it dynamic so users can change date ranges)
- `level`: `campaign` (options: account, campaign, adset, ad)
- `limit`: `100` (maximum results per page)

Change 'Use as' to 'Data' since this is a read call.

Click 'Initialize call'. Replace the `ad_account_id` placeholder with your real ad account ID (without act_ — the `act_` is already in the path pattern). Submit. Bubble makes the real API request and detects the `data` array response.

Note the `actions` field in the response — it returns an array of conversion event objects, each with `action_type` and `value`. The specific action type for purchases varies by pixel configuration — it might be `purchase`, `offsite_conversion.fb_pixel_purchase`, or another value. Read the raw actions array on your first initialization to identify the correct action_type string for your pixel.

```
{
  "method": "GET",
  "url": "https://graph.facebook.com/v19.0/act_<dynamic: ad_account_id>/insights",
  "parameters": {
    "access_token": "<private>",
    "fields": "campaign_name,spend,impressions,clicks,ctr,cpm,actions",
    "date_preset": "last_30d",
    "level": "campaign",
    "limit": "100"
  },
  "example_response": {
    "data": [
      {
        "campaign_name": "Summer Sale 2026",
        "spend": "142.87",
        "impressions": "18420",
        "clicks": "423",
        "ctr": "2.297167",
        "cpm": "7.756",
        "actions": [
          { "action_type": "purchase", "value": "12" },
          { "action_type": "lead", "value": "38" }
        ]
      }
    ]
  }
}
```

**Expected result:** The 'Get Campaign Insights' call is initialized. Bubble detects the 'data' array with campaign_name, spend, impressions, clicks, ctr, cpm, and actions fields. The call appears with a green checkmark in the API Connector.

### 4. Build the Campaign Performance Repeating Group

On your admin page in Bubble, add a Repeating Group element. Set Type to 'External API response'. Set Data Source to 'Get data from external API → Facebook Ads - Get Campaign Insights'. For the `ad_account_id` parameter, enter your account ID directly (you can also store it in a Config data type if you want it editable without changing the API Connector).

In the Repeating Group cells, add text elements and map them:
- Campaign Name: `Current cell's campaign_name`
- Spend: `Current cell's spend` (format as currency — note it comes as a string, so Bubble may need a 'converted to number' step)
- Impressions: `Current cell's impressions`
- Clicks: `Current cell's clicks`
- CTR: `Current cell's ctr` formatted as a number with 2 decimal places, followed by a '%' text element
- CPM: `Current cell's cpm` formatted as currency

For the `actions` field, filtering by action type in Bubble requires either a Backend Workflow that processes the array or client-side filtering on the repeating group using `:filtered` expressions. The simpler approach: create a Backend Workflow that calls the Insights API and saves results to a Bubble data type 'Ad Campaign', extracting specific actions from the array as separate fields.

**Expected result:** The repeating group populates with Facebook campaign data. Campaign names, spend, impressions, clicks, CTR, and CPM all display correctly with proper formatting.

### 5. Optionally: Set Up a Lead Ads Webhook Endpoint in Bubble

If you run Facebook Lead Ads (forms that appear directly within Facebook/Instagram), you can receive lead submissions in real-time via Bubble's Backend Workflows instead of manually exporting CSVs.

In your Bubble app, go to Settings → API → enable 'This app exposes a Workflow API'. Then go to Backend Workflows (in the Workflow tab → Backend Workflows section). Create a new API Workflow named 'Receive Meta Lead'. Under the workflow settings, check 'Expose as a public API endpoint'. Note the endpoint URL shown — it looks like `https://yourapp.bubbleapps.io/api/1.1/wf/receive-meta-lead`.

Click 'Detect request data'. Bubble opens a waiting state. You'll send a test payload to initialize the schema. You can use a tool like Postman or Bubble's own API test panel to POST this sample Lead Ads payload to the endpoint.

After detection, add workflow steps: 1) Call GET `/{{leadgen_id}}` to retrieve the full lead data from Meta (use the detected `leadgen_id` field from the webhook payload). 2) Create a new 'Lead' Thing in Bubble with the retrieved contact fields. 3) Optionally trigger a SendGrid email.

In Meta's Business Manager Developer app, go to Webhooks → Add subscription → select 'leadgen' as the object. Enter your Bubble endpoint URL and a verify_token (a random string you create). Meta will immediately send a GET request with `hub.verify_token` and `hub.challenge` to verify the endpoint. Add a 'Return data from API' step with the `hub.challenge` value to pass this verification.

Note: Backend Workflows require a paid Bubble plan. RapidDev's team has set up Lead Ads webhook integrations for many Bubble clients — for complex nurture flow needs, get in touch at rapidevelopers.com/contact.

```
{
  "meta_webhook_verification_get": {
    "hub.mode": "subscribe",
    "hub.verify_token": "your_verify_token_string",
    "hub.challenge": "1234567890"
  },
  "meta_leadgen_webhook_post": {
    "object": "page",
    "entry": [
      {
        "id": "<page_id>",
        "time": 1234567890,
        "changes": [
          {
            "value": {
              "leadgen_id": "<lead_id>",
              "page_id": "<page_id>",
              "ad_id": "<ad_id>",
              "form_id": "<form_id>"
            },
            "field": "leadgen"
          }
        ]
      }
    ]
  }
}
```

**Expected result:** The Backend Workflow endpoint is verified by Meta. Lead form submissions on Facebook/Instagram trigger the workflow within seconds. New Lead records appear in Bubble's database automatically.

## Best practices

- Always use a System User token (non-expiring) instead of a personal OAuth user token for any production Bubble integration with Facebook Ads — personal tokens expire silently after 60 days.
- Mark the access_token shared parameter as Private in the API Connector — without Private, the Meta token is visible in browser network requests and can be extracted.
- Include the act_ prefix in your API call path template rather than the account ID parameter — this prevents the common 'wrong format' error.
- Check the raw actions array on your first API call to identify your pixel's specific action_type strings before building conversion tracking UI.
- For Lead Ads, respond to Meta's webhook within 5 seconds. Use 'Return data from API' as step 1, then process lead data asynchronously to avoid timeout failures.
- Set up a simple monitoring page that shows the last successful API call timestamp — if it hasn't updated in 24 hours, investigate whether the token has been revoked.
- Use the `date_preset` parameter rather than custom date ranges for standard periods (last_7d, last_30d) — Insights API performance is better with presets than with raw date ranges.
- WU awareness: every Insights API call consumes Workload Units. For dashboards viewed frequently by your team, consider caching results in Bubble's database (refresh on schedule rather than every page load) to reduce WU costs.

## Use cases

### Campaign Performance Dashboard

Build a Bubble admin page that displays all active Facebook and Instagram campaigns with their key metrics — spend, impressions, clicks, CTR, CPM, and conversions — refreshed by calling the Insights API on page load. Give your team visibility into ad performance without sharing Ads Manager credentials.

Prompt example:

```
Add a Repeating Group on the Ad Dashboard page. Type = External API response, Data Source = Get data from external API → Facebook Ads 'Get Campaign Insights'. Display: Campaign Name (Current cell's campaign_name), Spend (Current cell's spend formatted as currency), Clicks (Current cell's clicks), CTR (Current cell's ctr formatted as percentage).
```

### Lead Ads Real-Time Sync

When a user submits a Meta Lead Form on Facebook or Instagram, receive the lead data immediately in Bubble via a Backend Workflow webhook endpoint. Automatically create a Lead record in Bubble's database, trigger a welcome email via SendGrid, and notify your sales team in Slack — all within seconds of form submission.

Prompt example:

```
Create a Backend Workflow 'Receive Meta Lead' exposed as API. When triggered, look up lead details via GET /leadgen/{leadgen_id} from the webhook payload. Create new Lead record with: Email = API response email, Name = API response full_name, Source = 'Facebook Lead Form', Created date = Now. Then run SendGrid 'Send Lead Welcome Email' action.
```

### Ad Account Budget Monitor

Query account-level spend using the Insights API with date_preset='this_month' to track monthly budget utilization. Display a budget progress bar in your Bubble dashboard by comparing current spend against a target spend field stored in Bubble's database. Alert via Intercom or email when spend exceeds 80% of the monthly target.

Prompt example:

```
Create a GAQL-style Insights call at the account level: GET /act_{accountId}/insights with fields=spend&date_preset=this_month&level=account. On Admin page, calculate spend percentage: (Current API response spend / App Config's monthly_budget_target) * 100. Show conditional progress bar that turns red above 80%.
```

## Troubleshooting

### Dashboard shows no data after 60 days with no error message

Cause: A personal user access token was used instead of a System User token. Personal tokens expire after 60 days of inactivity. The Insights API returns an empty response or an auth error that may not surface clearly in Bubble.

Solution: Generate a new System User token (never-expiring) in Business Manager → System Users and update the access_token value in the Bubble API Connector shared parameters. Going forward, always use System User tokens for production integrations.

### Insights API returns an error about the ad account ID format

Cause: The `act_` prefix is missing from the account ID in the API path. The path must be `/act_123456789/insights`, not `/123456789/insights`. Bubble passes the raw account ID without the prefix if not included in the path pattern.

Solution: In the API Connector call path, hardcode the `act_` prefix before the dynamic parameter: `/act_<dynamic: ad_account_id>/insights`. The account ID placeholder should contain only the numeric digits, not the `act_` prefix — the prefix is part of the path string.

### The actions field returns an array but I can't find the purchase conversion count

Cause: The specific action_type string for purchase conversions varies by pixel configuration. It could be 'purchase', 'offsite_conversion.fb_pixel_purchase', or another variant depending on how your Meta Pixel was set up.

Solution: On the first API call initialization, open the raw response in Bubble's API response inspector and read the full actions array to identify all action_type values. Find the one that represents purchases for your specific pixel. Use that exact string when filtering the actions in your Bubble repeating group.

### Meta webhook verification request fails — endpoint returns an error

Cause: Bubble's Backend Workflow must respond with the hub.challenge value from Meta's GET verification request. If the workflow doesn't have a 'Return data from API' step that returns the hub.challenge string, Meta considers verification failed.

Solution: In the Backend Workflow, add a 'Return data from API' step as the first action. In the response body, map the hub_challenge field from the detected request data. This must return the challenge string exactly — Meta checks that the response body matches what it sent.

### Backend Workflow option is missing — I can't create the Lead Ads webhook endpoint

Cause: Backend Workflows are only available on Bubble's paid plans. The free plan does not expose the Backend Workflows section.

Solution: Upgrade to Bubble's Starter plan ($32/month) to access Backend Workflows and the Workflow API endpoint feature. The webhook pattern for Lead Ads requires this feature. As an alternative on the free plan, you can set up a Zapier or Make.com step to receive the webhook and call a Bubble Data API to create the record — though this adds an external dependency.

## Frequently asked questions

### Why did my Facebook Ads dashboard stop showing data after a few months?

You almost certainly used a personal access token that expired. Personal OAuth user tokens expire after 60 days of inactivity (sometimes sooner). The solution going forward is to use a System User token from Business Manager, which can be generated without an expiry date. Replace the token in your Bubble API Connector shared parameters immediately.

### Do I need to create a Meta Developer App even if I'm not building an app for the App Store?

Yes. The Meta Developer App is required to generate System User tokens, regardless of whether you're building a consumer-facing application. Go to developers.facebook.com, create an app of type 'Business', and connect it to your Business Manager. This is a one-time setup step.

### Does Bubble's free plan support this integration?

The Insights API dashboard part works on any Bubble plan — the API Connector is available on free plans. The Lead Ads webhook pattern requires Backend Workflows, which are only on paid Bubble plans ($32/month Starter and above). If you only need campaign reporting, the free plan is sufficient.

### How do I get data for Instagram campaigns separately from Facebook?

Instagram campaigns are managed through Facebook Ads Manager and appear in the same Insights API response. Filter by publisher_platform using the breakdown parameter: add `breakdowns=publisher_platform` to your API call and the response will split data by Facebook, Instagram, and Audience Network. The account ID and auth are the same.

### Can I use this to create or modify Facebook Ad campaigns from Bubble?

Yes — the Meta Marketing API supports campaign creation, budget modifications, and ad management. You would need `ads_management` permission on your System User token (instead of just ads_read) and additional API calls for create/update operations. For a reporting-only dashboard, ads_read is sufficient.

### The actions array is confusing — how do I get just the purchase count?

The actions field returns an array of objects like [{"action_type":"purchase","value":"12"},{"action_type":"lead","value":"38"}]. In Bubble, you can filter a repeating group of the actions array to show only items where action_type = 'purchase'. Alternatively, create a Backend Workflow that saves the Insights response to Bubble's database, extracting specific action_type values as separate numeric fields — this makes the data easier to display and sort.

---

Source: https://www.rapidevelopers.com/bubble-integrations/facebook-ads
© RapidDev — https://www.rapidevelopers.com/bubble-integrations/facebook-ads
