To integrate Infusionsoft by Keap with Lovable, register an application in the Keap Developer portal to get OAuth2 credentials, complete the authorization flow, store all tokens in Cloud → Secrets, then build Edge Functions that call the Keap REST API for contacts, opportunities, tags, and automated email sequences. Note that Infusionsoft is the legacy Keap Max Classic product — if you are starting new, the modern Keap API is simpler.
Connect Lovable to Infusionsoft for tag-based CRM automation and email sequencing
Infusionsoft (officially rebranded as Keap Max Classic) is a small business CRM platform with a dedicated user base built on its powerful tag-based automation system. Tags in Infusionsoft are multi-purpose markers applied to contacts that can trigger email sequences, update pipeline stages, assign tasks, and segment contacts into lists for targeted campaigns. For Lovable apps serving customers who run their business on Infusionsoft, integration creates powerful automation opportunities: applying a tag when a user completes a trial triggers an automated sales follow-up sequence, while removing a tag when they upgrade stops the outreach automatically.
The authentication model is OAuth2 with access and refresh tokens. Infusionsoft and the modern Keap product share the same Developer portal (developer.infusionsoft.com / keys.developer.keap.com) and the same OAuth2 flow. The key distinction: Infusionsoft accounts (identified by the infusionsoft.com app domain) use the Keap REST API, which is a modern REST interface that replaced the legacy XML-RPC API. Do not use the XML-RPC API for new integrations.
Access tokens expire after 24 hours (longer than most OAuth2 APIs), and refresh tokens expire after 90 days of non-use. The Edge Function implements automatic token refresh using the refresh token and client credentials.
The most common Lovable + Infusionsoft integration patterns are: adding contacts to Infusionsoft when users sign up (triggering welcome email sequences), applying behavior-based tags from app events (user reached feature X, user hit usage limit), and managing opportunity pipelines that mirror your app's subscription stages.
Integration method
Infusionsoft by Keap has no native Lovable connector. All CRM and automation operations are proxied through Lovable Edge Functions that call the Keap REST API v1 using OAuth2 Bearer tokens. Client credentials and tokens are stored in Cloud → Secrets and accessed via Deno.env.get(), with the Edge Function handling token refresh using the stored refresh token.
Prerequisites
- A Lovable project with Lovable Cloud enabled (Edge Functions require a deployed app)
- An active Infusionsoft by Keap account (at keap.com/infusionsoft — this is the legacy Keap Max Classic product)
- Access to the Keap Developer portal at developer.infusionsoft.com to create an OAuth2 application
- Existing tags set up in your Infusionsoft account for key customer segments (new signup, trial, customer, etc.)
- Familiarity with Infusionsoft tag IDs — numeric IDs for each tag visible in Infusionsoft → CRM → Tags
Step-by-step guide
Register an application in the Keap Developer portal
Register an application in the Keap Developer portal
OAuth2 authentication for Infusionsoft uses the Keap Developer portal, which covers both the legacy Infusionsoft (Keap Max Classic) and the modern Keap product. Go to developer.infusionsoft.com and sign in with your Infusionsoft/Keap account. Click 'Register Application'. Fill in: - Application Name: 'Lovable Integration' - Description: something descriptive - Redirect URL: https://yourapp.lovable.app/oauth/callback (for the initial token exchange) - Application Type: Server (for OAuth2 client credentials/authorization code) After registration, you receive a Client ID and Client Secret. Copy both. Next, generate the initial access and refresh tokens via the OAuth2 authorization code flow. Construct the authorization URL: https://accounts.infusionsoft.com/app/oauth/authorize?client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URI&response_type=code&scope=full Open this URL in your browser and authorize the application. Infusionsoft redirects to your redirect URI with a 'code' parameter. Copy the code. Exchange the code for tokens: POST to https://api.infusionsoft.com/token with: - grant_type: authorization_code - client_id: your client ID - client_secret: your client secret - redirect_uri: your redirect URI - code: the authorization code Use Postman or Hoppscotch to make this request as form URL-encoded. The response includes access_token, refresh_token, token_type, and expires_in (86400 seconds = 24 hours). Note your Infusionsoft app name (the subdomain in yourappname.infusionsoft.com). Some API operations require passing the application name.
Pro tip: Infusionsoft access tokens are valid for 24 hours (not 1 hour like most OAuth2 APIs), which reduces refresh frequency. However, refresh tokens expire after 90 days of non-use — if your integration is inactive for 90 days, you need to re-authorize. Set a reminder to trigger at least one API call every 60 days to keep the refresh token active.
Expected result: A Keap Developer application is registered with a Client ID and Client Secret. You have completed the OAuth2 flow and obtained an access_token and refresh_token. All values are ready to store in Cloud → Secrets.
Store OAuth2 credentials and find your Infusionsoft tag IDs
Store OAuth2 credentials and find your Infusionsoft tag IDs
Add all credentials to Lovable's Cloud Secrets panel. In Lovable, click '+' → Cloud panel → Secrets. Add: - KEAP_CLIENT_ID — your application's Client ID from the developer portal - KEAP_CLIENT_SECRET — your application's Client Secret - KEAP_ACCESS_TOKEN — the current access token (valid for 24 hours) - KEAP_REFRESH_TOKEN — the long-lived refresh token (expires after 90 days of non-use) For tag-based automation, you also need the numeric tag IDs for your key segments. In Infusionsoft, go to CRM → Settings → Tags. Each tag shows its ID — a numeric value like 123 or 4567. Make a list of the tag IDs you will use: Store frequently-used tag IDs as additional secrets: - KEAP_TAG_NEW_SIGNUP — tag ID applied when users sign up - KEAP_TAG_TRIAL_ACTIVE — tag ID for active trial users - KEAP_TAG_TRIAL_EXPIRED — tag ID for expired trials - KEAP_TAG_CUSTOMER — tag ID for paying customers This approach means tag ID changes in Infusionsoft only require secret updates, not code changes in your Edge Functions. The Keap REST API base URL is https://api.infusionsoft.com/crm/rest/v1/ for the REST API.
Pro tip: Tag IDs in Infusionsoft are visible in the tag list URL when you hover over a tag: the number in the URL fragment (e.g., /app/tagmanager/viewAllGroups?tag=1234) is the tag ID. You can also retrieve all tags via GET /api/v1/tags to get a complete list with IDs.
Expected result: KEAP_CLIENT_ID, KEAP_CLIENT_SECRET, KEAP_ACCESS_TOKEN, KEAP_REFRESH_TOKEN, and any tag ID secrets appear in Cloud → Secrets.
Create the Keap REST API Edge Function with token refresh
Create the Keap REST API Edge Function with token refresh
Build the Edge Function that handles Infusionsoft/Keap operations with automatic OAuth2 token refresh. The Keap REST API v1 uses Bearer token authentication, similar to other OAuth2 APIs. Keap REST API key endpoints: - GET /crm/rest/v1/contacts — search contacts - POST /crm/rest/v1/contacts — create contact - PATCH /crm/rest/v1/contacts/{contactId} — update contact - POST /crm/rest/v1/contacts/{contactId}/tags — apply tags - DELETE /crm/rest/v1/contacts/{contactId}/tags — remove tags - GET /crm/rest/v1/tags — list all tags - GET /crm/rest/v1/opportunities — list opportunities For searching contacts by email: GET /crm/rest/v1/contacts?email=user@example.com. This returns a contacts array — check if any match before creating a new one. For applying tags: POST /crm/rest/v1/contacts/{contactId}/tags with body { tagIds: [123, 456] }. Note: tagIds (plural) is an array. The token refresh endpoint is POST https://api.infusionsoft.com/token with grant_type=refresh_token, client_id, client_secret, and refresh_token. The response includes a new access_token (and sometimes a new refresh_token — if a new refresh token is provided, the old one is invalidated, so you must update your stored value).
Create an Edge Function called keap-infusionsoft at supabase/functions/keap-infusionsoft/index.ts. Accept POST with { action, params }. Support actions: find_or_create_contact, apply_tags, remove_tags, list_tags, create_opportunity. Implement token refresh using KEAP_REFRESH_TOKEN, KEAP_CLIENT_ID, and KEAP_CLIENT_SECRET when a 401 is returned. Use KEAP_ACCESS_TOKEN as Bearer. Base URL: https://api.infusionsoft.com/crm/rest/v1. Include CORS headers.
Paste this in Lovable chat
1import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'23const corsHeaders = {4 'Access-Control-Allow-Origin': '*',5 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',6}78const KEAP_BASE = 'https://api.infusionsoft.com/crm/rest/v1'910async function refreshKeapToken(): Promise<string> {11 const resp = await fetch('https://api.infusionsoft.com/token', {12 method: 'POST',13 headers: { 'Content-Type': 'application/x-www-form-urlencoded' },14 body: new URLSearchParams({15 grant_type: 'refresh_token',16 client_id: Deno.env.get('KEAP_CLIENT_ID')!,17 client_secret: Deno.env.get('KEAP_CLIENT_SECRET')!,18 refresh_token: Deno.env.get('KEAP_REFRESH_TOKEN')!,19 }),20 })21 const data = await resp.json()22 if (!data.access_token) throw new Error('Keap token refresh failed')23 return data.access_token24}2526async function keapFetch(path: string, method = 'GET', body?: unknown, retry = true): Promise<Response> {27 let token = Deno.env.get('KEAP_ACCESS_TOKEN')!28 let resp = await fetch(`${KEAP_BASE}${path}`, {29 method,30 headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },31 body: body ? JSON.stringify(body) : undefined,32 })33 if (resp.status === 401 && retry) {34 token = await refreshKeapToken()35 resp = await fetch(`${KEAP_BASE}${path}`, {36 method,37 headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },38 body: body ? JSON.stringify(body) : undefined,39 })40 }41 return resp42}4344serve(async (req) => {45 if (req.method === 'OPTIONS') return new Response('ok', { headers: corsHeaders })4647 try {48 const { action, params = {} } = await req.json()49 let resp: Response5051 switch (action) {52 case 'find_or_create_contact': {53 // First try to find by email54 const searchResp = await keapFetch(`/contacts?email=${encodeURIComponent(params.email)}`)55 const searchData = await searchResp.json()56 if (searchData.contacts?.length > 0) {57 return new Response(JSON.stringify({ contactId: searchData.contacts[0].id, created: false }), {58 headers: { ...corsHeaders, 'Content-Type': 'application/json' },59 })60 }61 // Create if not found62 resp = await keapFetch('/contacts', 'POST', {63 given_name: params.firstName,64 family_name: params.lastName,65 email_addresses: [{ email: params.email, field: 'EMAIL1' }],66 phone_numbers: params.phone ? [{ number: params.phone, field: 'PHONE1' }] : [],67 company: { company_name: params.company || '' },68 tag_ids: params.tagIds || [],69 })70 const created = await resp.json()71 return new Response(JSON.stringify({ contactId: created.id, created: true }), {72 headers: { ...corsHeaders, 'Content-Type': 'application/json' },73 })74 }7576 case 'apply_tags':77 resp = await keapFetch(`/contacts/${params.contactId}/tags`, 'POST', { tagIds: params.tagIds })78 break7980 case 'remove_tags':81 resp = await keapFetch(`/contacts/${params.contactId}/tags`, 'DELETE', { tagIds: params.tagIds })82 break8384 case 'list_tags':85 resp = await keapFetch('/tags?limit=200')86 break8788 default:89 return new Response(JSON.stringify({ error: 'Unknown action' }), {90 status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' },91 })92 }9394 const data = await resp.json()95 return new Response(JSON.stringify(data), {96 status: resp.ok ? 200 : resp.status,97 headers: { ...corsHeaders, 'Content-Type': 'application/json' },98 })99 } catch (error) {100 return new Response(JSON.stringify({ error: error.message }), {101 status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' },102 })103 }104})Pro tip: Infusionsoft contact email addresses use a field identifier ('EMAIL1', 'EMAIL2', 'EMAIL3') rather than a type label. Always use 'EMAIL1' as the field for the primary email. Similarly, phone numbers use 'PHONE1', 'PHONE2', etc.
Expected result: The keap-infusionsoft Edge Function deploys in Cloud → Edge Functions. Test with action 'list_tags' — your Infusionsoft tags with their numeric IDs should appear in the response.
Wire tag-based automation to your Lovable app events
Wire tag-based automation to your Lovable app events
With the Edge Function deployed, connect it to the key events in your Lovable app where tag-based automation should trigger. The pattern is consistent: detect the event in your React component or server-side logic, call the Edge Function with the contact's email and the relevant tag IDs, and let Infusionsoft's automation engine handle the follow-up sequences. For user registration: call find_or_create_contact after Supabase Auth creates the account, then apply the KEAP_TAG_NEW_SIGNUP tag. Use the fire-and-forget pattern (no await) so Infusionsoft sync does not delay the registration success response. For trial expiry: create a Supabase database function or scheduled Edge Function that runs daily, queries users whose trial_expires_at is in the past, and calls apply_tags with the KEAP_TAG_TRIAL_EXPIRED tag for each affected user's Infusionsoft contact. For subscription upgrades: in your payment webhook handler (or Supabase triggers on subscription updates), call apply_tags with KEAP_TAG_CUSTOMER and remove_tags with KEAP_TAG_TRIAL_ACTIVE. This atomic tag swap stops the trial nurture sequence and starts the customer success sequence simultaneously. Store the Infusionsoft contact ID in your Supabase users table after the first find_or_create call. Subsequent tag operations use the contact ID directly rather than searching by email each time — this avoids a search API call on every event.
After user registration in my app, call the keap-infusionsoft Edge Function with action 'find_or_create_contact' using the new user's email, firstName, and lastName. Then call 'apply_tags' with the contact ID and the KEAP_TAG_NEW_SIGNUP value from environment config. Store the returned contactId in the users Supabase table. Do all of this after the Supabase Auth signup succeeds, using fire-and-forget pattern.
Paste this in Lovable chat
Pro tip: Build a simple admin page in your Lovable app that shows a contact's current tags and lets you manually apply or remove tags for testing. This makes it much faster to verify that your automation sequences are triggering correctly without waiting for real user events.
Expected result: New user registrations create Infusionsoft contacts and apply the signup tag. The contact appears in Infusionsoft with the tag visible in their contact record. Any automation sequences triggered by that tag start running automatically.
Common use cases
Apply tags to contacts based on user behavior in your app
When users reach specific milestones in your Lovable app — completing onboarding, reaching a usage threshold, or requesting a demo — apply corresponding tags to their Infusionsoft contact record. Tags trigger the automation sequences you have built in Infusionsoft: a 'demo-requested' tag starts a sales follow-up sequence; 'onboarding-complete' tag stops the onboarding drip and starts a feature adoption series; 'trial-expired' tag triggers a reactivation campaign.
Create an Edge Function called infusionsoft-tag that accepts { email, tagIds } and applies the specified tag IDs to the Infusionsoft contact matching that email using KEAP_ACCESS_TOKEN from secrets. If the contact does not exist, create it first. Return the contact ID and confirmation. Call this when a user completes onboarding (pass the onboarding-complete tag ID) and when a trial expires (pass the trial-expired tag ID).
Copy this prompt to try it in Lovable
Create contacts and start email sequences on user signup
When a new user registers in your Lovable app, create an Infusionsoft contact with their information and apply your 'new-signup' tag to trigger your welcome email sequence. The automation runs in Infusionsoft without any additional manual steps, sending timed emails over the days following signup based on your sequence configuration. This replaces manual list exports with real-time automation.
Create an Edge Function called infusionsoft-create-contact that accepts { firstName, lastName, email, company, phone, plan, source } and creates an Infusionsoft contact via the Keap REST API using KEAP_ACCESS_TOKEN from secrets. Apply the signup tag ID (from KEAP_SIGNUP_TAG_ID secret) to trigger the welcome sequence. Return the contact ID. Call this from my registration success handler.
Copy this prompt to try it in Lovable
Sync subscription upgrades to Infusionsoft opportunity pipeline
When a user upgrades their subscription tier in your Lovable app, update their Infusionsoft opportunity record to reflect the new stage and value. Remove the 'trial' tag and apply the 'paying-customer' tag to stop outbound nurturing emails and start customer success sequences. This keeps your sales pipeline and marketing automation synchronized with actual subscription state.
Create an Edge Function called infusionsoft-upgrade that accepts { email, newPlan, monthlyValue } and updates the Infusionsoft contact's opportunity stage and value using KEAP_ACCESS_TOKEN from secrets. Remove tag KEAP_TRIAL_TAG_ID and apply KEAP_CUSTOMER_TAG_ID. Update the opportunity stage to 'Customer'. Return success. Call this when a Supabase subscription record changes from trial to paid.
Copy this prompt to try it in Lovable
Troubleshooting
Keap API returns 401 Unauthorized even with a freshly obtained access token
Cause: The access token has expired (24-hour lifetime) or the token stored in Cloud → Secrets is outdated.
Solution: Verify the token refresh logic in your Edge Function is working by checking Cloud → Logs for the Edge Function. If the refresh call itself is failing, verify KEAP_REFRESH_TOKEN, KEAP_CLIENT_ID, and KEAP_CLIENT_SECRET are all correctly set in Cloud → Secrets. Note that if the refresh token has not been used for 90 days, it expires and you must complete the full OAuth2 authorization flow again.
Contact creation succeeds but tags are not applied
Cause: Tags passed in the contact creation body (tag_ids array) may not be applied automatically by all API versions. Tag application may require a separate POST call.
Solution: After creating a contact, make a separate apply_tags call using the returned contact ID and the tag IDs. Do not rely on the tag_ids field in the contact creation body — make the tag application as an explicit second operation to ensure reliability.
find_or_create_contact returns a duplicate contact instead of finding the existing one
Cause: The email search is not finding the existing contact because email addresses in Infusionsoft use the EMAIL1/EMAIL2 field identifiers, and the contact's email may be stored in a different field.
Solution: The Keap REST API email search (GET /contacts?email=...) searches across all email fields. If duplicates persist, verify the existing contact has a correctly stored email address by checking the contact directly in Infusionsoft. Use the contact's email_addresses array in the API response to identify which field (EMAIL1, EMAIL2) holds the address.
Tag removal (DELETE /contacts/{id}/tags) returns 404 or has no effect
Cause: The DELETE method requires the tag IDs to be in the request body, which is not supported by all HTTP clients by default.
Solution: Ensure the DELETE request includes the JSON body { tagIds: [...] }. Some HTTP implementations strip the body from DELETE requests — verify in Cloud → Logs that the body is being sent. The Keap REST API requires the tag IDs in the body for tag deletion operations.
Best practices
- Store Infusionsoft tag IDs as named secrets (KEAP_TAG_NEW_SIGNUP, KEAP_TAG_CUSTOMER) rather than hardcoding numeric IDs in code — tag IDs are account-specific and change with account restructuring.
- Store the Infusionsoft contact ID in your Supabase users table after first creation to avoid repeated email lookup calls on every tag operation.
- Keep the refresh token active by ensuring your integration triggers at least one API call every 60 days — Keap refresh tokens expire after 90 days of non-use.
- Use atomic tag swaps (apply new tag + remove old tag in the same Edge Function invocation) for state transitions like trial-to-customer to avoid brief periods of incorrect automation state.
- Implement fire-and-forget for Infusionsoft calls in user-facing flows — Infusionsoft sync failures should not block the primary user experience.
- Test automation sequences with a test contact before wiring to production user events — create a test contact, apply the trigger tag, and verify the automation sequence fires as expected in Infusionsoft.
- Log the Infusionsoft contact ID and applied tag IDs in your Supabase audit table for troubleshooting — automation issues are much easier to debug when you can trace which tags were applied at which time.
Alternatives
Choose Keap (the modern product) if you are starting a new integration — Keap's REST API v2 is simpler than the Infusionsoft API and represents the future direction of the Keap platform.
Choose HubSpot if you are not locked into the Infusionsoft ecosystem — HubSpot's free CRM tier and simpler Private App authentication make it a better starting point for new integrations.
Choose Pipedrive if you primarily need sales pipeline management rather than tag-based marketing automation — Pipedrive's API is simpler and more focused on pure CRM operations.
Frequently asked questions
What is the difference between Infusionsoft by Keap and the modern Keap product?
Infusionsoft is the legacy product, now officially named Keap Max Classic. It has the original Infusionsoft feature set with the tag-based automation engine, campaign builder, and e-commerce features. The modern Keap product (formerly Keap Grow and Keap Pro) has a simpler, more modern interface with a streamlined API (REST v2). Existing Infusionsoft customers stay on Keap Max Classic. New customers typically start with the modern Keap product. Both use the same Developer portal for OAuth2 applications.
Do Infusionsoft tags work as automation triggers in real time?
Yes — Infusionsoft tag-based automation triggers fire in real time when a tag is applied or removed. If you have a campaign sequence configured to start when tag 'trial-expired' is applied, applying that tag via the API triggers the sequence immediately. The automation engine checks tag changes continuously, not on a scheduled basis. This makes tag-based triggers from Lovable events a reliable real-time automation mechanism.
Can I use the old Infusionsoft XML-RPC API instead of the REST API?
You can, but you should not for new integrations. The Infusionsoft XML-RPC API is the legacy interface that predates REST. The Keap REST API v1 covers all major operations and is significantly easier to work with in TypeScript/JavaScript. The XML-RPC API requires parsing XML responses and constructing XML request bodies, which is substantially more complex in Deno Edge Functions. The REST API is the only interface receiving new features and official support.
How do I prevent Infusionsoft from sending duplicate welcome emails to returning users?
Use the find_or_create_contact pattern: search for the contact by email before creating a new one. Only apply the 'new-signup' tag if the contact was newly created (created: true in the response). If the contact already exists, check their existing tags before applying new ones — you may want to apply a 'returning-user' tag instead of 'new-signup' to trigger a different re-engagement sequence.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation