Connect Bubble to Infusionsoft by Keap using the modern Keap REST API v2 (not the legacy XML-RPC) via Bubble's API Connector with OAuth 2.0 authorization_code flow. Register a Keap Developer app, store tokens server-side using a Backend Workflow, and build a custom CRM portal for contact management, tag-based segmentation, and campaign enrollment — all without touching Keap's dated UI.
| Fact | Value |
|---|---|
| Tool | Infusionsoft by Keap |
| Category | CRM & Sales |
| Method | Bubble API Connector |
| Difficulty | Intermediate |
| Time required | 2–3 hours |
| Last updated | July 2026 |
Keap REST v2 — the only path that works in Bubble
Infusionsoft launched as an email marketing automation tool in 2001 and accumulated a complex SOAP/XML-RPC API over the years. When the brand rebranded to Keap in 2019, it released a modern REST API v2 at `https://api.infusionsoft.com/crm/rest/v2` using standard JSON and OAuth 2.0 Bearer tokens. This REST v2 API is fully compatible with Bubble's API Connector. The legacy XML-RPC API is NOT — it uses XML payloads with method call wrappers that Bubble cannot produce or parse.
The OAuth flow requires a one-time browser-redirect authorization (the user — or you, as the app owner — logs in to Keap and grants access). After that, Bubble's Backend Workflow handles token refresh silently, storing the current access_token in your User record. Every API Connector call then reads this token as a dynamic Private parameter.
The primary Bubble use case is a custom CRM portal: small business owners want a clean, branded Bubble interface for their team to manage contacts, assign tags, and enroll contacts in campaigns — instead of using Keap's own dated UI. The tag-based segmentation model is powerful: each tag represents a campaign state, and moving a contact into or out of a tag sequence triggers Keap's automations. Bubble surfaces this logic through visual workflows rather than Keap's legacy automation builder.
Integration method
Bubble API Connector with Keap REST API v2 OAuth 2.0 authorization_code flow — client_id and client_secret registered in the Keap Developer portal, access and refresh tokens stored server-side in Bubble via a Backend Workflow.
Prerequisites
- A Keap Pro or Max account — the REST API v2 is not available on the Keap Lite plan
- Access to the Keap Developer portal at keys.developer.keap.com to register an OAuth application
- The API Connector plugin installed in your Bubble app (Plugins tab → Add plugins → search 'API Connector' → Install)
- A Bubble paid plan (Starter or above) — Backend Workflows required for OAuth token exchange and refresh are not available on Free
- A designated OAuth callback page in your Bubble app to receive the authorization code after the Keap login redirect
Step-by-step guide
Register a Keap Developer OAuth app and get client credentials
Go to keys.developer.keap.com and sign in with your Keap account credentials. Click 'Create New App' (or 'Register an Application' depending on the portal version). Give your app a name such as 'Bubble CRM Integration.' In the app settings, find the Redirect URI field. Enter your Bubble app's OAuth callback URL — the page you will create in the next step to receive the authorization code. The format is: `https://yourappname.bubbleapps.io/oauth_callback` (or your custom domain if you have one). If using a custom domain, make sure HTTPS is configured. After saving, the portal shows your **Client ID** and **Client Secret**. Copy both now — the Client Secret is only shown once in some portal versions. Store them securely outside of Bubble until you configure the API Connector. Note: Keap REST API v2 requires a Pro or Max subscription. If your account is on Lite, you will see authentication errors even with a valid OAuth app. Verify your Keap plan in Account Settings before proceeding. The Keap authorization URL you will use in the next step is: `https://accounts.infusionsoft.com/app/oauth/authorize` The token endpoint is: `https://api.infusionsoft.com/token`
1// Keap OAuth 2.0 endpoints:2// Authorization URL: https://accounts.infusionsoft.com/app/oauth/authorize3// Token URL: https://api.infusionsoft.com/token4// Scopes: full56// Authorization request (browser redirect):7// GET https://accounts.infusionsoft.com/app/oauth/authorize8// ?client_id=YOUR_CLIENT_ID9// &redirect_uri=https://yourapp.bubbleapps.io/oauth_callback10// &response_type=code11// &scope=fullPro tip: Use a dedicated Keap account (not your personal login) for the Developer portal OAuth app. If the OAuth app owner's Keap account is ever deactivated, all tokens issued by that app stop working immediately.
Expected result: You have a Keap OAuth app registered with your Bubble redirect URI, and you have copied the Client ID and Client Secret.
Configure the Bubble API Connector with Keap OAuth 2.0
In your Bubble editor, click Plugins in the left sidebar and locate the API Connector plugin (install it from 'Add plugins' if it is not already present). Click 'Add another API' and name it 'Keap CRM.' In the API group settings, click 'Use as' and select 'Action.' Set the Authentication type to 'OAuth2 User-Agent.' Fill in the following fields: - **App name**: Keap CRM - **Authorization URL**: `https://accounts.infusionsoft.com/app/oauth/authorize` - **Access token URL**: `https://api.infusionsoft.com/token` - **Client ID**: paste your Keap Developer app Client ID - **Client Secret**: paste your Client Secret — ensure 'Private' is checked (this field is Private by default in OAuth2 setup) - **Scope**: `full` - **Redirect URL**: `https://yourappname.bubbleapps.io/oauth_callback` Bubble's OAuth2 User-Agent type will handle the browser redirect flow and token exchange automatically. However, because refresh tokens also need to be stored persistently for background API calls, you will supplement Bubble's built-in OAuth with a Backend Workflow (next step). Next, add a shared header at the API group level: `Authorization` with value `Bearer <access_token>` — mark the value as a dynamic parameter named `access_token` and check 'Private.' This shared header will appear on all calls within this API group. Add your first call: click 'Add another call,' name it 'Get Contacts,' set method to GET, URL to `https://api.infusionsoft.com/crm/rest/v2/contacts`. Add URL parameters: `given_name` (text, optional), `email_address` (text, optional), `limit` (number, default 25). Click 'Initialize call.' You must supply a valid access_token in the Private parameter field during initialization — use a token from the Keap API sandbox or run the authorization flow first. The response should return `{ "contacts": [...] }` — Bubble will detect the contacts array for list binding.
1// API Connector: Keap CRM → Get Contacts2// Method: GET3// URL: https://api.infusionsoft.com/crm/rest/v2/contacts4//5// Headers (shared at API group level):6// Authorization: Bearer <access_token> // mark Private7//8// URL Parameters:9// given_name: <dynamic_first_name> // optional10// email_address: <dynamic_email> // optional11// limit: 25 // max results per request12// offset: 0 // for pagination13//14// Example response structure:15// {16// "contacts": [17// {18// "id": 123,19// "given_name": "Jane",20// "family_name": "Smith",21// "email_addresses": [{"email": "jane@example.com", "field": "EMAIL1"}],22// "tag_ids": [10, 20, 30]23// }24// ]25// }Pro tip: Keap's response wraps contacts in a `contacts` key (not `data` or `results`). During the Initialize call, Bubble detects this key and sets the list binding root correctly. If you re-initialize after modifying the response shape, the old detected fields persist — click 'Reset' next to detected fields and re-initialize with a fresh response.
Expected result: The Keap CRM API group is configured in Bubble with OAuth2 settings and the Authorization header marked Private. The Get Contacts call initializes successfully and Bubble detects the contacts array.
Build a Backend Workflow to exchange the authorization code for tokens
After the Keap authorization redirect, Bubble receives an authorization code as a URL parameter on your OAuth callback page. You need a Backend Workflow that exchanges this code for an access_token and refresh_token, then stores both in the current User's data record. First, create a custom data type field on the User object: go to Data tab → User type → add a field `keap_access_token` (text), `keap_refresh_token` (text), and `keap_token_expiry` (date). Next, go to Settings → API → check 'This app exposes a Workflow API.' Then open the Backend Workflows section and click 'Add workflow.' Name it `keap_token_exchange`. In this Backend Workflow, add a 'Detect data' step to capture two incoming parameters: `code` (text) and `user_id` (text) — you will pass the current user's Bubble ID when calling this workflow. Add an API Connector action: call the Keap token endpoint. Create a separate API Connector call named 'Exchange Token' with method POST, URL `https://api.infusionsoft.com/token`, Content-Type `application/x-www-form-urlencoded`, and body parameters: - `grant_type` = `authorization_code` - `client_id` = your Client ID (static, not private since it is not a secret) - `client_secret` = your Client Secret (mark Private) - `code` = `<dynamic_code>` (from incoming parameter) - `redirect_uri` = your callback URL After the token exchange call returns, add 'Make changes to a Thing' to update the User record (found by `user_id`) with the returned `access_token`, `refresh_token`, and the expiry time (Current date/time + 86400 seconds ≈ 24 hours, though Keap tokens may vary). On your OAuth callback page, add a page-load workflow: when the page loads and URL parameter `code` is not empty, call 'Schedule API Workflow' to trigger `keap_token_exchange` at Current date/time, passing `code` = Page URL parameter `code` and `user_id` = Current User's unique ID.
1// API Connector: Keap → Exchange Token2// Method: POST3// URL: https://api.infusionsoft.com/token4// Content-Type: application/x-www-form-urlencoded5//6// Body parameters (form-encoded):7// grant_type=authorization_code8// client_id=YOUR_CLIENT_ID9// client_secret=YOUR_CLIENT_SECRET // mark Private10// code=<dynamic_authorization_code> // from URL parameter11// redirect_uri=https://yourapp.bubbleapps.io/oauth_callback12//13// Successful response:14// {15// "access_token": "eyJ...",16// "token_type": "bearer",17// "expires_in": 86400,18// "refresh_token": "rtoken...",19// "scope": "full"20// }2122// Token refresh call (same endpoint, different grant_type):23// grant_type=refresh_token24// refresh_token=<stored_refresh_token>25// client_id=YOUR_CLIENT_ID26// client_secret=YOUR_CLIENT_SECRETPro tip: Store the `keap_token_expiry` timestamp in the User record so your pre-call check can compare it to 'Current date/time.' If the token is within 5 minutes of expiring, trigger the refresh Backend Workflow proactively — before the actual API call fails with a 401 error.
Expected result: After the Keap authorization redirect, the Backend Workflow fires automatically, exchanges the code for tokens, and stores access_token and refresh_token in the User's data record. Subsequent API calls can read the current User's keap_access_token as the Authorization header value.
Create contacts and apply tags for campaign segmentation
With OAuth tokens stored, you can now build the core CRM actions. The two most important write operations are creating a contact and applying tags. **Creating a contact:** Add a new API Connector call under Keap CRM, name it 'Create Contact,' method POST, URL `https://api.infusionsoft.com/crm/rest/v2/contacts`. The JSON body should include the basic contact fields: ``` { "given_name": "<dynamic_first_name>", "family_name": "<dynamic_last_name>", "email_addresses": [ { "email": "<dynamic_email>", "field": "EMAIL1" } ], "phone_numbers": [ { "number": "<dynamic_phone>", "field": "PHONE1" } ] } ``` The response includes the new contact's `id` — store this in a Custom State or Bubble database field if you need to immediately apply tags. **Applying tags:** Add another call named 'Apply Tags,' method POST, URL `https://api.infusionsoft.com/crm/rest/v2/contacts/<contact_id>/tags`. The body must be a JSON array: `{ "tagIds": [<tag_id_1>, <tag_id_2>] }`. The `tagIds` value must be a JSON array of integers — not a comma-separated string and not a single integer. To get available tag IDs, add a 'Get Tags' call: GET `https://api.infusionsoft.com/crm/rest/v2/tags` — this returns all tags with their `id` and `name`. You can populate a Bubble dropdown with this list and let users select tags by name, then pass the numeric IDs in the apply-tags action. In your Bubble workflow, the sequence is: (1) call Create Contact, (2) extract the returned `id` from the response and store in a state, (3) call Apply Tags passing the stored ID and selected tag IDs. All three steps run server-side inside a Backend Workflow for security.
1// API Connector: Keap CRM → Create Contact2// Method: POST3// URL: https://api.infusionsoft.com/crm/rest/v2/contacts4// Authorization: Bearer <access_token> // Private5// Content-Type: application/json6//7// Body:8{9 "given_name": "<dynamic_first_name>",10 "family_name": "<dynamic_last_name>",11 "email_addresses": [12 { "email": "<dynamic_email>", "field": "EMAIL1" }13 ],14 "phone_numbers": [15 { "number": "<dynamic_phone>", "field": "PHONE1" }16 ]17}1819// API Connector: Keap CRM → Apply Tags20// Method: POST21// URL: https://api.infusionsoft.com/crm/rest/v2/contacts/<contact_id>/tags22// Authorization: Bearer <access_token> // Private23// Content-Type: application/json24//25// Body (MUST be a JSON array of integers, not a string):26{27 "tagIds": [42, 107, 215]28}Pro tip: The Keap `tagIds` field must be a JSON array of integers. In Bubble, when you build this body dynamically, use a Bubble list field containing the selected tag IDs. If you pass a string like '42,107' or a single number outside an array, Keap returns a 400 Bad Request. Test the body structure in Initialize call mode before wiring it to a live workflow.
Expected result: A new Keap contact is created via the POST /contacts call. The Apply Tags call immediately enrolls the contact in the associated Keap campaign sequence. Both calls return 200 or 201 with the created/updated resource in the response body.
Build the contact search UI with a Bubble Repeating Group
Now surface Keap contact data in your Bubble app's UI. The primary pattern is a search page with a Text Input for name/email and a Repeating Group displaying results. On your CRM page, add a Text Input element for search. Add a Button labeled 'Search' and a Repeating Group below it. Set the Repeating Group's data type to 'External API' and its data source to the result of the 'Get Contacts' API Connector call. Wire the search button's click workflow to 'Reset relevant inputs' and then set the Repeating Group's data source dynamically. In the API Connector Get Contacts call, pass the Text Input's value as the `given_name` or `email_address` parameter. Because Keap searches these fields separately, consider building two calls (one for name, one for email) or using the `email_address` parameter when the input looks like an email (contains '@'). Inside each row of the Repeating Group, display: - Contact name: `Current cell's contacts's given_name` + `family_name` - Email: `Current cell's contacts's email_addresses's first item's email` - Tags: a nested Repeating Group or comma-joined list of tag names For tag display, you need to join the numeric `tag_ids` from the contact response with the tag names from your 'Get Tags' call. Cache the tag list in a Custom State on page load to avoid repeated API calls per row — calling Get Tags inside the Repeating Group row would fire once per visible contact, rapidly consuming API rate limits. Add a 'Load more' button that increments the `offset` parameter by 25 on each click to implement pagination.
1// Page workflow: Search button clicked2// Action 1: Reset group - Repeating Group Contacts3// Action 2: Refresh API Connector result4// Call: Keap CRM - Get Contacts5// given_name: Search Input's value (if not email-format)6// email_address: Search Input's value (if contains @)7// limit: 258// offset: 0910// Repeating Group data source:11// Type of content: Keap CRM - Get Contacts - contacts12// Data source: Get data from external API → Keap CRM - Get Contacts1314// For tag display inside each row:15// Do NOT call Get Tags per row — load once on page load:16// Page load workflow → Get Tags → store result in Custom State 'All Tags'17// In row: filter All Tags list where tag id is in Current cell's tag_idsPro tip: Cache the Keap tag list in a Custom State on page load instead of fetching it inside each Repeating Group row. Loading tags per row multiplies your API calls by the number of visible contacts — a 25-row list generates 25 tag calls on every search, burning through Keap's rate limits quickly.
Expected result: Your Bubble CRM page shows a searchable list of Keap contacts. The search filters by name or email via the Keap REST API. Each row shows the contact's name, email, and tag list. Clicking 'Load more' fetches the next page of results.
Handle token refresh and test the complete integration
Keap access tokens expire — build a proactive refresh mechanism so your app never fails mid-session with a 401 error. **Token refresh Backend Workflow:** Create a workflow named `keap_refresh_token`. It calls the Keap token endpoint with `grant_type=refresh_token` and the current User's stored refresh_token. On success, it updates the User's `keap_access_token` and `keap_token_expiry`. **Pre-call check:** Before every Keap API call, add a condition check in your workflow: if `Current User's keap_token_expiry` is less than 5 minutes from now, run the `keap_refresh_token` Backend Workflow first, then proceed with the API call. In Bubble workflow terms: add a 'Schedule API Workflow' action for the refresh, followed by a 'Pause before next action' set to 2 seconds, then the actual Keap call. This is not perfectly atomic but works for most use cases. **Testing the end-to-end flow:** 1. In your Bubble preview, navigate to a page with a 'Connect Keap' button 2. The button triggers the OAuth authorization URL as an external link (Target = '_blank') 3. Log in to Keap and authorize the app 4. Keap redirects back to your OAuth callback page with a `?code=` parameter 5. The page-load workflow fires `keap_token_exchange` 6. Verify tokens are stored: Data tab → User → check your test user's keap_access_token field 7. Navigate to the CRM search page and run a contact search 8. Check Bubble Logs tab → Workflow logs to confirm the API call returned 200 For debugging, use the Logs tab → Workflow logs to inspect each step. If a call returns 401, check that the access_token in the User record is not expired and that the Authorization header is reading from the correct field. RapidDev's team has built OAuth-connected Bubble apps with integrations like this across dozens of client projects — if you get stuck on the token flow, reach out for a free scoping call at rapidevelopers.com/contact.
1// Token refresh API call:2// Method: POST3// URL: https://api.infusionsoft.com/token4// Content-Type: application/x-www-form-urlencoded5//6// Body:7// grant_type=refresh_token8// refresh_token=<Current User's keap_refresh_token> // Private9// client_id=YOUR_CLIENT_ID10// client_secret=YOUR_CLIENT_SECRET // Private11//12// Bubble workflow sequence for pre-call token check:13// Condition: Current User's keap_token_expiry < Current date/time + 5 minutes14// YES → Schedule API Workflow: keap_refresh_token (at Current date/time)15// Pause before next action: 2 seconds16// [Then proceed with Keap API call]17// NO → [Proceed directly with Keap API call]Pro tip: The Keap Developer portal may show a 'Sandbox' environment for testing. Use sandbox credentials during development so you do not pollute your production contact database. Sandbox tokens work only against sandbox endpoints — switch to production credentials and production endpoints before going live.
Expected result: The complete OAuth flow works end-to-end: authorization redirect, code exchange, token storage, API calls with live tokens, and automatic token refresh before expiry. The Bubble Logs tab shows successful 200 responses from all Keap REST API calls.
Common use cases
Custom branded contact management portal
Build a Bubble CRM portal that lets your sales team view, search, and update Keap contacts without logging into the Keap UI. Staff see a clean Repeating Group of contacts with name, email, tags, and last-contacted date pulled live from Keap's REST API. Inline forms let them add notes and update contact fields without leaving Bubble.
On page load, call GET /contacts with filters for given_name and email_address from a Bubble search field. Display results in a Repeating Group showing contact_name, email, and tag list. When a row is clicked, open a popup with a detail view and an Edit button that POSTs updated fields back to Keap.
Copy this prompt to try it in Bubble
Tag-based campaign enrollment from a Bubble intake form
Customers fill out a Bubble intake form, triggering a Keap contact creation followed immediately by tag application. The tags enroll the new contact into the relevant Keap email sequence automatically. The entire flow runs server-side in a Bubble Backend Workflow — zero manual Keap intervention.
When a form is submitted, first POST to /contacts to create the record, extract the returned contact id, then POST to /contacts/{id}/tags with tagIds matching the intake category selected. Store the Keap contact_id in Bubble's User record for future updates.
Copy this prompt to try it in Bubble
Sales pipeline opportunity tracker
Pull Keap opportunity records into a Bubble pipeline view showing stage, value, and probability. Sales reps drag cards between Bubble columns to update stages, and the change is written back to Keap via PATCH /opportunities/{id} — keeping Keap's native reporting accurate while giving reps a modern UI.
On pipeline page load, call GET /opportunities to fetch open deals. Render them in Bubble's Repeating Group sorted by stage. On a drag-and-drop or dropdown change, call PATCH /opportunities/{id} with the updated stage_name and close_date fields to sync back to Keap.
Copy this prompt to try it in Bubble
Troubleshooting
API Connector returns 401 Unauthorized even though the access_token looks correct
Cause: The access_token has expired (Keap tokens have a limited lifespan) or the token belongs to a Keap account on the Lite plan — the REST API v2 requires Pro or Max. A 401 can also result from using the legacy XML-RPC endpoint URL instead of the REST v2 base URL.
Solution: First, verify your Keap account plan is Pro or Max at keap.com/pricing. Second, check that the base URL starts with `https://api.infusionsoft.com/crm/rest/v2/` — not `https://api.infusionsoft.com/xmlrpc` or any other legacy path. Third, trigger your `keap_refresh_token` Backend Workflow manually to get a fresh access_token and test again.
Apply Tags call returns 400 Bad Request
Cause: The `tagIds` value is formatted incorrectly. Common mistakes in Bubble: passing a single integer instead of an array, passing a comma-separated string like '42,107', or leaving the field empty. Keap requires a JSON array of integers at all times.
Solution: In the API Connector body for the Apply Tags call, set the `tagIds` field to a Bubble list expression that produces a JSON array. In Bubble's dynamic body editor, use the list of tag ID numbers formatted as a JSON array. Click 'Initialize call' with a test value like `[42]` (a single-item array) to verify the format Keap accepts. Confirm in the Initialize response that the call returns 204 or 200 without a 400 error.
1// Correct tagIds format:2{ "tagIds": [42, 107] }34// Wrong formats that return 400:5{ "tagIds": "42,107" } // string, not array6{ "tagIds": 42 } // integer, not arrayBackend Workflow does not fire when the OAuth callback page loads
Cause: The Backend Workflow API endpoint is not enabled (Settings → API → 'This app exposes a Workflow API' is unchecked), or the page-load workflow condition is wrong. The workflow fires only when the URL parameter `code` is not empty — if the condition check is missing, the workflow may not trigger.
Solution: Go to Settings → API and confirm 'This app exposes a Workflow API' is checked. On the OAuth callback page, open the Workflow editor and verify the page-load workflow has the condition: 'Only when: URL parameter code is not empty.' Also confirm the workflow is calling 'Schedule API Workflow' targeting `keap_token_exchange`, not a client-side action (which cannot run Backend Workflows).
Initialize call fails with 'There was an issue setting up your call'
Cause: Bubble's Initialize call requires a real successful API response to detect the response schema. If you have not yet completed the OAuth flow (no valid access_token), or if the test access_token you paste during initialization is expired, the call returns 401 and Bubble cannot detect fields.
Solution: Complete the full OAuth authorization flow at least once to get a valid access_token. Temporarily paste the fresh access_token directly into the Private field value during Initialize call mode. After initialization succeeds and Bubble detects the response fields, clear the hardcoded value and replace it with the dynamic expression reading from the current User's keap_access_token field.
Keap redirects back with an error in the URL (e.g., ?error=invalid_client) instead of a code
Cause: The client_id or client_secret in the authorization URL do not match what is registered in the Keap Developer portal, or the redirect_uri in the authorization URL does not exactly match the one registered in the app (including trailing slashes and http vs https).
Solution: Check the Keap Developer portal at keys.developer.keap.com for the exact registered redirect_uri. Compare it character-by-character with the redirect_uri in your API Connector OAuth settings — a single trailing slash difference causes this error. Also verify the client_id is correct (not swapped with the client_secret).
Best practices
- Always use Keap REST API v2 (`api.infusionsoft.com/crm/rest/v2`) — the legacy XML-RPC endpoint is incompatible with Bubble's JSON API Connector and is no longer recommended for new integrations.
- Store the Keap access_token and refresh_token in the User's Bubble data record, never in a Custom State or page-level variable — states are lost on page navigation, which would force users to re-authorize Keap on every page load.
- Mark both client_secret and access_token as Private in Bubble's API Connector — the client_secret must never reach the browser, and the access_token carries full account permissions.
- Cache the Keap tag list in a Custom State on page load and reuse it across Repeating Group rows — fetching tags per row multiplies API calls by the number of visible contacts and can trigger Keap's rate limits quickly.
- Build proactive token refresh logic: compare the stored `keap_token_expiry` timestamp to 'Current date/time + 5 minutes' before each API call, and run the refresh Backend Workflow if the token is about to expire.
- Add Bubble privacy rules to any data type that stores Keap contact data (name, email, tag_ids) — go to Data tab → Privacy and create rules ensuring contacts are readable only by the User who owns them or by logged-in admin roles.
- Test all Keap API calls in your Bubble development environment before deploying — initialize calls with real tokens, verify response shapes, and confirm tag IDs are correct. Bubble's Logs tab → Workflow logs shows the full request and response details for debugging.
Alternatives
HubSpot offers a more modern CRM API with a free tier that includes API access — Keap requires a paid Pro or Max plan for API. HubSpot also has a dedicated Bubble plugin available in the marketplace. Choose Keap if you are already using its email marketing automation sequences and want to surface the tag-based enrollment workflow in Bubble.
Pipedrive uses a simple API token with no OAuth redirect flow — significantly easier to configure in Bubble. Pipedrive focuses on sales pipeline management without email marketing automation. Choose Pipedrive if you need a straightforward sales CRM without Keap's marketing automation complexity.
Mailchimp is a dedicated email marketing tool with list and tag management — similar to Keap's campaign sequence model but without the CRM contact management features. Choose Mailchimp if your primary need is email campaigns rather than a full contact CRM with custom fields and opportunities.
Frequently asked questions
Can I use the legacy Infusionsoft XML-RPC API with Bubble?
No. Bubble's API Connector only supports JSON-based REST APIs. The legacy Infusionsoft XML-RPC endpoint uses XML method-call wrappers that Bubble cannot produce or parse natively. Always use the Keap REST API v2 at `https://api.infusionsoft.com/crm/rest/v2` for Bubble integrations. Older guides online describing XML-RPC for Infusionsoft are outdated and will not work with Bubble.
Do I need a paid Bubble plan to connect to Keap?
Yes. The OAuth token exchange and token refresh logic must run in Backend Workflows, which are only available on Bubble's Starter plan or above. A free Bubble plan cannot run Backend Workflows, making the OAuth flow impossible to complete securely. The Keap API itself also requires a Pro or Max Keap subscription — Lite plan does not include REST API access.
How do I display tag names instead of tag IDs in my Bubble app?
Call GET /tags on page load and store the result in a Custom State as a list. When displaying contacts in a Repeating Group, filter the cached tag list to find tags where the ID matches any item in the contact's `tag_ids` field. Display the matched tag's `name` field. This approach uses one API call for the full tag list rather than one call per row.
What happens if a user's Keap access token expires mid-session?
Without a refresh mechanism, your API calls will start returning 401 errors and the user will see empty data or error states. Build a token-refresh Backend Workflow and trigger it proactively before each API call when the stored `keap_token_expiry` is within 5 minutes of the current time. This ensures seamless background token rotation without interrupting the user's session.
Can I connect multiple Keap accounts to a single Bubble app?
Yes, but it requires storing separate credentials per user or per 'account' data type. Each user who authorizes Keap generates their own access_token and refresh_token, stored in their User record. Your Bubble workflows read the current user's tokens for all API calls. This multi-tenant pattern works for agency tools where each client has their own Keap account.
How do I unenroll a contact from a Keap tag sequence?
Use DELETE `/contacts/{id}/tags` with a body containing the `tagIds` array to remove specific tags. Removing a tag does not automatically stop an in-progress Keap email sequence — you may also need to use the Keap Campaign Goal endpoints to update sequence state. Check the Keap REST API v2 documentation for the campaign-level endpoints relevant to your specific automation setup.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation