Skip to main content
RapidDev - Software Development Agency
replit-integrationsStandard API Integration

How to Integrate Replit with Keap

To integrate Replit with Keap, create an API application in the Keap developer console to get OAuth 2.0 credentials or a personal access token, store them in Replit Secrets (lock icon πŸ”’), and call the Keap REST API from your Python or Node.js server to manage contacts, orders, and marketing automation sequences. Autoscale deployment works well for event-driven CRM updates.

What you'll learn

  • How to create a Keap API application and obtain OAuth 2.0 or personal access token credentials
  • How to store Keap credentials securely in Replit Secrets
  • How to create and manage contacts and tags using Python and Node.js
  • How to programmatically trigger Keap automation sequences from your Replit app
  • How to manage orders and products via the Keap e-commerce API
Book a free consultation
4.9Clutch rating ⭐
600+Happy partners
17+Countries served
190+Team members
Intermediate14 min read30 minutesMarketingMarch 2026RapidDev Engineering Team
TL;DR

To integrate Replit with Keap, create an API application in the Keap developer console to get OAuth 2.0 credentials or a personal access token, store them in Replit Secrets (lock icon πŸ”’), and call the Keap REST API from your Python or Node.js server to manage contacts, orders, and marketing automation sequences. Autoscale deployment works well for event-driven CRM updates.

Why Connect Replit to Keap?

Keap is the CRM of choice for solopreneurs and small business owners who want automated follow-up sequences without a large marketing team. The combination of CRM contacts, tag-based automations, e-commerce order tracking, and appointment scheduling in a single platform makes Keap uniquely powerful for service businesses. Connecting your Replit app to Keap lets you automate the data flow that Keap's native integrations do not cover β€” adding contacts from custom forms, triggering automation sequences based on product events, and syncing order data from external payment systems.

The most powerful feature of the Keap API is the tag and automation system. In Keap, applying a tag to a contact immediately triggers any automation sequence configured to start on that tag. This means your Replit app can trigger complex multi-step follow-up sequences simply by applying the right tag β€” no need to call campaign-specific API endpoints.

Replit's Secrets system (lock icon πŸ”’ in the sidebar) is where you store Keap credentials. For simple personal automations, Keap's Personal Access Tokens are the easiest approach β€” a single long-lived token with no OAuth flow required. For applications serving multiple users or businesses (common in agency scenarios), full OAuth 2.0 is required. Never include Keap credentials in client-side JavaScript, as the API key grants full access to your CRM including all contact and order data.

Integration method

Standard API Integration

You connect Replit to Keap by creating an application in the Keap developer console to get OAuth 2.0 credentials, storing client credentials and access tokens in Replit Secrets, and calling the Keap REST API from server-side Python or Node.js code. For development and personal automations, Keap also offers personal access tokens (PATs) that skip the OAuth flow. The API base URL is https://api.infusionsoft.com/crm/rest/v1.

Prerequisites

  • A Replit account with a Python or Node.js project created
  • A Keap account (Lite, Pro, or Max tier β€” all include API access)
  • Access to the Keap developer console at developer.keap.com
  • Basic familiarity with REST APIs and OAuth 2.0
  • Python 3.10+ or Node.js 18+ (both available on Replit by default)

Step-by-step guide

1

Obtain Keap API Credentials

For development and personal automation workflows, the easiest approach is a Keap Personal Access Token (PAT). Visit developer.keap.com, sign in with your Keap account, and navigate to 'Personal Access Tokens'. Click 'New Token', give it a descriptive name like 'replit-integration', and copy the generated token. PATs are long-lived and work immediately without any OAuth flow. For production applications that serve multiple Keap accounts (such as a SaaS app), you need a full OAuth 2.0 application. At developer.keap.com, click 'Create New App', fill in the app name and description, and add a redirect URI for your deployed Replit URL (e.g., https://your-app.replit.app/oauth/callback). The portal provides a Client ID and Client Secret. Open your Replit project and click the lock icon πŸ”’ in the left sidebar. For PAT-based auth, add one secret: KEAP_API_KEY with your personal access token. For OAuth, add KEAP_CLIENT_ID, KEAP_CLIENT_SECRET, and KEAP_REFRESH_TOKEN (obtained after completing the OAuth flow once). The Keap API base URL is https://api.infusionsoft.com/crm/rest/v1 and all endpoints use Bearer token authentication.

Pro tip: Personal Access Tokens are perfect for server-to-server integrations where only your Keap account is involved. Use OAuth 2.0 only if you are building a multi-tenant application that needs to access different Keap accounts on behalf of multiple users.

Expected result: Your Keap API credentials are stored in Replit Secrets and accessible as environment variables. The KEAP_API_KEY (or OAuth tokens) appear in the Secrets pane.

2

Manage Contacts and Tags in Python

The Keap REST API base URL is https://api.infusionsoft.com/crm/rest/v1. Authentication uses a Bearer token β€” whether that is a PAT or an OAuth access token, the header format is identical: Authorization: Bearer {token}. The tag system is the most powerful automation trigger in Keap. When you apply a tag to a contact via the API (POST /contacts/{id}/tags), any automation sequence configured to start on that tag begins immediately. This makes tag application the primary integration pattern: your Replit app applies the right tag at the right moment, and Keap handles the entire downstream follow-up sequence. The Python code below covers the core operations: finding a contact by email, creating a new contact, applying and removing tags, and creating a note on a contact record. The upsert pattern β€” search first, update if found, create if not β€” prevents duplicate contact records.

keap_client.py
1import os
2import requests
3from typing import Optional
4
5API_KEY = os.environ["KEAP_API_KEY"]
6BASE_URL = "https://api.infusionsoft.com/crm/rest/v1"
7
8HEADERS = {
9 "Authorization": f"Bearer {API_KEY}",
10 "Content-Type": "application/json"
11}
12
13def find_contact_by_email(email: str) -> Optional[dict]:
14 """Search for an existing contact by email address."""
15 params = {"email": email, "limit": 1}
16 response = requests.get(f"{BASE_URL}/contacts", params=params, headers=HEADERS)
17 response.raise_for_status()
18 contacts = response.json().get("contacts", [])
19 return contacts[0] if contacts else None
20
21def create_contact(email: str, first_name: str, last_name: str = "",
22 phone: str = "") -> dict:
23 """Create a new contact in Keap."""
24 payload = {
25 "email_addresses": [{"email": email, "field": "EMAIL1"}],
26 "given_name": first_name,
27 "family_name": last_name,
28 "duplicate_option": "Email" # Merge if email exists
29 }
30 if phone:
31 payload["phone_numbers"] = [{"number": phone, "field": "PHONE1"}]
32
33 response = requests.post(f"{BASE_URL}/contacts", json=payload, headers=HEADERS)
34 response.raise_for_status()
35 return response.json()
36
37def apply_tag(contact_id: int, tag_id: int) -> dict:
38 """Apply a tag to a contact (triggers any automation sequences on that tag)."""
39 payload = {"tagIds": [tag_id]}
40 response = requests.post(
41 f"{BASE_URL}/contacts/{contact_id}/tags",
42 json=payload,
43 headers=HEADERS
44 )
45 response.raise_for_status()
46 return response.json()
47
48def remove_tag(contact_id: int, tag_id: int) -> None:
49 """Remove a tag from a contact."""
50 response = requests.delete(
51 f"{BASE_URL}/contacts/{contact_id}/tags/{tag_id}",
52 headers=HEADERS
53 )
54 response.raise_for_status()
55
56def get_tags() -> list:
57 """Get all tags in the Keap account."""
58 response = requests.get(f"{BASE_URL}/tags", headers=HEADERS)
59 response.raise_for_status()
60 return response.json().get("tags", [])
61
62def create_note(contact_id: int, title: str, body: str,
63 note_type: str = "Other") -> dict:
64 """Add a note to a contact record."""
65 payload = {
66 "contact_id": contact_id,
67 "title": title,
68 "body": body,
69 "type": note_type # Appointment, Call, Email, Fax, Letter, Meeting, Other
70 }
71 response = requests.post(f"{BASE_URL}/notes", json=payload, headers=HEADERS)
72 response.raise_for_status()
73 return response.json()
74
75# Example usage
76if __name__ == "__main__":
77 # Get all tags to find the right ID
78 tags = get_tags()
79 print("Available tags:")
80 for tag in tags[:10]:
81 print(f" {tag['id']}: {tag['name']}")
82
83 # Create or find a contact
84 contact = find_contact_by_email("test@example.com")
85 if not contact:
86 contact = create_contact("test@example.com", "Test", "User")
87 print(f"Contact created: ID {contact['id']}")
88 else:
89 print(f"Contact found: ID {contact['id']}")
90
91 # Apply a tag to trigger automation (replace 123 with real tag ID)
92 if tags:
93 apply_tag(contact['id'], tags[0]['id'])
94 print(f"Tag applied: {tags[0]['name']}")

Pro tip: Call GET /tags at the start of your integration to print all tag IDs and names. Store the IDs you need in Replit Secrets (e.g., KEAP_ONBOARDING_TAG_ID) rather than hardcoding numbers, so they are easy to update without changing code.

Expected result: The script prints your Keap tags, creates or finds a test contact, and applies a tag. The contact appears in Keap with the tag applied and any associated automation sequence begins.

3

Build a Node.js Server with Order Management

The Node.js integration uses the same Bearer token authentication as Python. The Express server below exposes a contact enrollment endpoint and an order creation endpoint, covering the two most common Keap integration patterns: inbound lead capture and order syncing from external payment processors. Keap's order API lets you create orders linked to contacts and associate them with product records. This is useful when you process payments outside of Keap (e.g., via Stripe) but want the purchase data visible in the CRM for post-purchase automation sequences. Creating an order in Keap automatically marks the contact as a 'customer' and can trigger purchase-specific automations. Install dependencies with 'npm install express axios' in the Replit shell. The server also implements a basic search-before-create pattern in the /enroll endpoint to prevent duplicate contact records.

server.js
1const express = require('express');
2const axios = require('axios');
3
4const app = express();
5app.use(express.json());
6
7const API_KEY = process.env.KEAP_API_KEY;
8const ONBOARDING_TAG_ID = parseInt(process.env.KEAP_ONBOARDING_TAG_ID || '0');
9const BASE_URL = 'https://api.infusionsoft.com/crm/rest/v1';
10
11const keap = axios.create({
12 baseURL: BASE_URL,
13 headers: {
14 'Authorization': `Bearer ${API_KEY}`,
15 'Content-Type': 'application/json'
16 }
17});
18
19async function findOrCreateContact(email, firstName, lastName) {
20 // Search for existing contact
21 const searchResp = await keap.get('/contacts', { params: { email, limit: 1 } });
22 const contacts = searchResp.data.contacts || [];
23 if (contacts.length > 0) return contacts[0];
24
25 // Create new contact
26 const createResp = await keap.post('/contacts', {
27 email_addresses: [{ email, field: 'EMAIL1' }],
28 given_name: firstName,
29 family_name: lastName || '',
30 duplicate_option: 'Email'
31 });
32 return createResp.data;
33}
34
35// Enroll a new lead and apply onboarding tag
36app.post('/enroll', async (req, res) => {
37 const { email, firstName, lastName, source } = req.body;
38 if (!email || !firstName) {
39 return res.status(400).json({ error: 'email and firstName are required' });
40 }
41
42 try {
43 const contact = await findOrCreateContact(email, firstName, lastName);
44 const contactId = contact.id;
45
46 // Apply onboarding tag to trigger automation sequence
47 if (ONBOARDING_TAG_ID) {
48 await keap.post(`/contacts/${contactId}/tags`, {
49 tagIds: [ONBOARDING_TAG_ID]
50 });
51 }
52
53 // Add a source note
54 if (source) {
55 await keap.post('/notes', {
56 contact_id: contactId,
57 title: 'Lead Source',
58 body: `Contact enrolled from: ${source}`,
59 type: 'Other'
60 });
61 }
62
63 res.json({ success: true, contactId });
64 } catch (err) {
65 console.error('Enroll error:', err.response?.data || err.message);
66 res.status(500).json({ error: err.message });
67 }
68});
69
70// Create an order from an external payment
71app.post('/orders', async (req, res) => {
72 const { email, productName, amount, orderId } = req.body;
73 if (!email || !amount) {
74 return res.status(400).json({ error: 'email and amount are required' });
75 }
76
77 try {
78 // Find the contact
79 const searchResp = await keap.get('/contacts', { params: { email, limit: 1 } });
80 const contacts = searchResp.data.contacts || [];
81 if (!contacts.length) {
82 return res.status(404).json({ error: 'Contact not found in Keap' });
83 }
84
85 const contactId = contacts[0].id;
86
87 // Create the order
88 const orderResp = await keap.post('/orders', {
89 contact_id: contactId,
90 order_date: new Date().toISOString(),
91 order_items: [{
92 description: productName || 'Product Purchase',
93 price: amount,
94 quantity: 1
95 }],
96 title: orderId ? `Order ${orderId}` : 'External Order',
97 source_type: 'ONLINE'
98 });
99
100 res.json({ success: true, orderId: orderResp.data.id });
101 } catch (err) {
102 console.error('Order error:', err.response?.data || err.message);
103 res.status(500).json({ error: err.message });
104 }
105});
106
107app.get('/health', (req, res) => res.json({ status: 'ok' }));
108
109app.listen(3000, '0.0.0.0', () => {
110 console.log('Keap integration server running on port 3000');
111});

Pro tip: Test the /enroll endpoint with a real email address from your Keap account to verify that the automation sequence fires after the tag is applied. Check the contact's automation history in Keap to confirm the sequence started.

Expected result: The server starts and POST /enroll creates a contact in Keap, applies the onboarding tag, and returns the contactId. The automation sequence tied to the tag begins running in Keap.

4

Deploy and Configure Webhook Triggers

Keap supports outbound webhooks (called 'Hook' subscriptions in the API) that POST to your Replit server when contacts or orders change. This enables bidirectional integration β€” your app can react to Keap events like tag removal (unsubscribing from a sequence) or order creation (fulfilling a purchase). To create a webhook subscription programmatically, call POST /hooks with the event_key (e.g., 'contact.add', 'order.add', 'contact.delete') and your webhook URL. Alternatively, set up webhooks in the Keap UI under Settings > API > Webhooks. Keap sends a JSON POST to your endpoint with the hook event and the affected record's ID. Deploy your Replit app before registering webhook URLs. Click 'Deploy' in the Replit editor and choose Autoscale for event-driven CRM workflows. Copy the stable deployment URL (e.g., https://your-app.replit.app) and use it as the webhook target. Keap webhooks include a X-Hook-Secret header on the initial verification request β€” respond with the secret value to confirm the endpoint.

webhook_server.py
1from flask import Flask, request, jsonify
2import os
3import hmac
4import hashlib
5
6app = Flask(__name__)
7HOOK_SECRET = os.environ.get("KEAP_HOOK_SECRET", "")
8
9@app.route('/keap/webhook', methods=['POST'])
10def keap_webhook():
11 # Verify hook secret on first delivery
12 hook_secret = request.headers.get('X-Hook-Secret')
13 if hook_secret:
14 # Keap sends X-Hook-Secret on first delivery for verification
15 return hook_secret, 200, {'X-Hook-Secret': hook_secret}
16
17 data = request.get_json(force=True)
18 if not data:
19 return jsonify({'error': 'No data'}), 400
20
21 event_key = data.get('event_key', 'unknown')
22 object_keys = data.get('object_keys', [])
23
24 print(f"Keap webhook: {event_key}, IDs: {object_keys}")
25
26 if event_key == 'contact.add':
27 for contact_id in object_keys:
28 print(f"New contact added: {contact_id}")
29 # Fetch full contact data if needed
30
31 elif event_key == 'order.add':
32 for order_id in object_keys:
33 print(f"New order created: {order_id}")
34 # Trigger fulfillment workflow
35
36 return jsonify({'status': 'received'}), 200
37
38@app.route('/health', methods=['GET'])
39def health():
40 return jsonify({'status': 'ok'}), 200
41
42if __name__ == '__main__':
43 app.run(host='0.0.0.0', port=3000)

Pro tip: Keap webhook payloads only include the IDs of affected records, not the full record data. Make a follow-up GET request to /contacts/{id} or /orders/{id} to retrieve the full details you need.

Expected result: Your deployed Replit server receives Keap webhook events and logs the event type and record IDs. The /health endpoint returns 200.

Common use cases

Contact Creation and Sequence Enrollment from Web Forms

When a visitor fills out a lead generation form on your Replit-hosted website, automatically create or update the Keap contact record, apply the appropriate intake tag, and trigger a pre-built follow-up sequence. The automated sequence sends personalized emails and tasks to the sales team without any manual intervention.

Replit Prompt

Build a Flask form handler that creates a Keap contact from form submission data stored in Replit Secrets, applies a 'webinar-signup' tag using the tag ID, and returns a success message to the user.

Copy this prompt to try it in Replit

E-commerce Order Sync from External Store

When a purchase is completed in your Replit e-commerce app or an external payment processor, create a Keap order linked to the contact's account and apply a 'customer' tag. Keap's post-purchase automation sequences automatically send receipts, onboarding emails, and upsell sequences based on the product purchased.

Replit Prompt

Write a Node.js webhook handler that receives Stripe payment confirmation events and creates a corresponding Keap order with the product and total amount, then applies a 'paying-customer' tag to the contact.

Copy this prompt to try it in Replit

Behavioral Trigger Automations

Your Replit app monitors user behavior β€” inactivity periods, feature adoption milestones, or subscription renewals β€” and applies specific Keap tags to trigger the matching automation sequence. This creates a closed loop between product usage data and marketing follow-up without requiring Keap to have direct access to your application database.

Replit Prompt

Create a Python script that queries your database for users who haven't logged in for 7 days, finds their Keap contact by email, and applies a 're-engagement' tag to trigger the win-back automation sequence.

Copy this prompt to try it in Replit

Troubleshooting

API returns 401 Unauthorized with 'Invalid or missing auth token'

Cause: The Bearer token in the Authorization header is invalid, expired, or missing. Personal access tokens do not expire but OAuth access tokens expire after one hour.

Solution: Verify the KEAP_API_KEY Secret in Replit Secrets contains the correct personal access token with no extra whitespace. If using OAuth, implement token refresh using the refresh token. Test the token by calling GET /profile (the simplest endpoint) to confirm it works.

typescript
1# Python: test your token
2response = requests.get(
3 "https://api.infusionsoft.com/crm/rest/v1/profile",
4 headers={"Authorization": f"Bearer {os.environ['KEAP_API_KEY']}"}
5)
6print(response.status_code, response.json())

Tag application returns 404 β€” tag not found

Cause: The tag ID does not exist in the Keap account. Tag IDs are account-specific integers that cannot be transferred between accounts.

Solution: Call GET /tags to list all available tags with their IDs. Update your KEAP_ONBOARDING_TAG_ID or equivalent Replit Secret with the correct integer ID. Create any missing tags via POST /tags before applying them.

typescript
1# List all tags
2response = requests.get(
3 f"{BASE_URL}/tags",
4 headers=HEADERS
5)
6for tag in response.json().get('tags', []):
7 print(f"Tag {tag['id']}: {tag['name']}")

Contact creation returns 400 with 'duplicate contact' error

Cause: A contact with the same email already exists in Keap. The API rejects duplicate emails by default unless duplicate_option is set.

Solution: Include 'duplicate_option': 'Email' in the contact creation payload to merge with an existing record. Alternatively, search for the contact first and update the existing record if found.

typescript
1# Python: use duplicate_option to merge
2payload = {
3 "email_addresses": [{"email": email, "field": "EMAIL1"}],
4 "given_name": first_name,
5 "duplicate_option": "Email" # Merge on email match
6}

Webhook events are not arriving at the Replit server

Cause: The webhook URL points to a development Replit session that goes offline when the IDE is closed, or the endpoint did not respond to the initial verification request with the X-Hook-Secret header.

Solution: Deploy your Replit app first to get a stable URL. Ensure your webhook handler returns the X-Hook-Secret header value in its response to the first POST request from Keap, which serves as endpoint verification. After deploying, re-register the webhook URL in Keap settings.

Best practices

  • Store KEAP_API_KEY and tag IDs in Replit Secrets (lock icon πŸ”’) β€” never hardcode API tokens or tag IDs directly in source code.
  • Use Personal Access Tokens for single-account server-to-server integrations to avoid the OAuth flow complexity β€” PATs are simpler to manage and do not expire.
  • Always include 'duplicate_option': 'Email' when creating contacts to prevent duplicate records when the same person submits a form multiple times.
  • Retrieve tag IDs dynamically via GET /tags rather than hardcoding them, and store the IDs you need as named Secrets for easy updating.
  • Use tag application as your primary automation trigger β€” applying a tag is simpler and more reliable than calling campaign-specific API endpoints.
  • Deploy your Replit app before registering webhook URLs with Keap β€” stable deployment URLs prevent dead webhook registrations.
  • Handle Keap's webhook payload format correctly: the payload only includes record IDs, so you must make a follow-up GET request to retrieve full record data.
  • Use Autoscale deployment for CRM update integrations and Reserved VM if you need guaranteed webhook delivery without cold-start delays.

Alternatives

Frequently asked questions

How do I store my Keap API key in Replit?

Click the lock icon πŸ”’ in the left sidebar of your Replit project to open the Secrets pane. Add KEAP_API_KEY with your Keap Personal Access Token (from developer.keap.com > Personal Access Tokens). Access it in Python with os.environ['KEAP_API_KEY'] or in Node.js with process.env.KEAP_API_KEY.

What is the difference between a Keap Personal Access Token and OAuth?

A Personal Access Token (PAT) is a long-lived token that gives access to a single Keap account with no expiration and no OAuth flow required. OAuth 2.0 is needed when your app must access multiple Keap accounts (e.g., a SaaS app serving multiple businesses). For server-to-server integrations with your own Keap account, PATs are the simpler and recommended approach.

Does Replit work with Keap on the free plan?

Yes. The Keap REST API is available on all Keap plans including Lite. Replit's free tier supports outbound API calls to Keap without restrictions. You will need Replit's paid plan for always-on deployments required for webhook reception, since free Replit projects sleep after inactivity.

How do I trigger a Keap automation sequence from Replit?

Apply the tag associated with the automation sequence to the contact via POST /contacts/{id}/tags with the tag ID in the tagIds array. Any Keap automation configured to start when that tag is applied will begin immediately after the API call. This is the primary automation trigger pattern and is simpler than calling campaign-specific endpoints.

Can I create orders in Keap from external payment processors like Stripe?

Yes. Use POST /orders to create an order linked to a contact ID. Include the product name, price, and quantity in the order_items array. First find the contact by email using GET /contacts?email={email}, then use the contact's ID in the order creation payload. This keeps your purchase history visible in Keap for post-purchase automations.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation β€” no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.