To integrate Replit with SharpSpring, store your SharpSpring Account ID and Secret Key in Replit Secrets (lock icon π) and call the SharpSpring REST API from your server-side Node.js or Python code. SharpSpring uses an unusual API style β requests are JSON-RPC-like POST calls with accountID and secretKey in the request body. From Replit you can manage leads, contacts, opportunities, email lists, and trigger automation workflows. Use Autoscale deployment for webhook event receivers.
Marketing Automation and CRM Integration from Replit with SharpSpring
SharpSpring is positioned squarely at the marketing agency market β it offers comprehensive marketing automation (email, CRM, landing pages, social, analytics) with white-label branding options that let agencies resell it under their own brand. This agency focus means the platform is feature-rich but its API has some quirks that differ from typical REST APIs. Most notably, SharpSpring's REST API uses a JSON-RPC-like style where all requests POST to a single endpoint with a 'method' parameter specifying what operation to perform. Authentication uses Account ID and Secret Key embedded in the request body rather than headers.
For Replit integrations, SharpSpring's API covers the full marketing and sales funnel: lead capture (creating and updating lead records with scores and custom fields), opportunity management (deals linked to contacts in the CRM), list management (segmenting contacts for targeted campaigns), and email automation (triggering emails and managing campaign memberships). The combination makes it possible to build complete lead nurturing automation from external data sources.
SharpSpring also supports webhooks for real-time event delivery. When a contact fills out a SharpSpring form, a lead score threshold is crossed, or an email campaign generates activity, SharpSpring can POST event data to your Replit server. For these webhooks to deliver reliably, your app must be deployed on Autoscale or Reserved VM β not running in Replit's development mode, which goes offline when you close the browser.
Integration method
SharpSpring integrates with Replit through SharpSpring's REST API using Account ID and Secret Key authentication embedded in the request body. Unlike typical REST APIs that use header-based auth, SharpSpring requires accountID and secretKey in the JSON request body alongside the API method and parameters. Your Replit backend POSTs to a single API endpoint with different method names to perform different operations β creating leads, updating opportunities, managing lists, and triggering workflows.
Prerequisites
- A Replit account with a Node.js or Python Repl ready
- A SharpSpring account β agency accounts include API access; contact SharpSpring for trial API credentials
- Your SharpSpring Account ID and Secret Key from Settings β API Settings in SharpSpring
- For webhooks: your app deployed with a stable HTTPS URL (https://yourapp.replit.app)
Step-by-step guide
Get Your SharpSpring API Credentials and Store Them in Replit Secrets
Get Your SharpSpring API Credentials and Store Them in Replit Secrets
SharpSpring API credentials consist of two values: an Account ID (a numeric string identifying your SharpSpring account) and a Secret Key (a longer alphanumeric string that authenticates requests). Both are required for every API call β neither alone is sufficient. To find your credentials: log into SharpSpring β click the gear icon (Settings) β API Settings (or navigate to Settings β API Settings). You will see your Account ID and can view or regenerate your Secret Key. If API access is not enabled on your account, contact SharpSpring support β API access is available on most agency plans. SharpSpring also requires an API endpoint URL that includes a session ID generated from your credentials. The standard endpoint is: https://api.sharpspring.com/pubapi/v1/?accountID={ACCOUNT_ID}&secretKey={SECRET_KEY}. However, for security, you should construct this URL in code from Replit Secrets rather than storing the full URL. Open your Replit project and click the lock icon (π) in the left sidebar. Add: SHARPSPRING_ACCOUNT_ID: your numeric SharpSpring Account ID. SHARPSPRING_SECRET_KEY: your SharpSpring Secret Key. Do not embed these credentials directly in your endpoint URL string in code β always build the URL from environment variables so the credentials never appear in source files.
1// check-sharpspring-secrets.js2const required = ['SHARPSPRING_ACCOUNT_ID', 'SHARPSPRING_SECRET_KEY'];3for (const key of required) {4 if (!process.env[key]) {5 throw new Error(`Missing: ${key}. Add it in Replit Secrets (lock icon π).`);6 }7}8console.log('SharpSpring credentials configured.');9console.log('Account ID:', process.env.SHARPSPRING_ACCOUNT_ID);10console.log('Secret Key prefix:', process.env.SHARPSPRING_SECRET_KEY.slice(0, 8) + '...');Pro tip: SharpSpring's Secret Key can be regenerated in Settings β API Settings. If you suspect a key has been compromised, regenerate it immediately and update SHARPSPRING_SECRET_KEY in Replit Secrets β and redeploy your Replit app so it picks up the new value.
Expected result: Both secrets appear in Replit Secrets pane. The verification script prints the Account ID and key prefix without errors.
Make API Calls with SharpSpring's JSON-RPC-Style Interface
Make API Calls with SharpSpring's JSON-RPC-Style Interface
SharpSpring's REST API works differently from most APIs. All requests POST to a single URL and use a 'method' parameter in the request body to specify the operation. The URL includes the Account ID and Secret Key as query parameters, and the body contains a JSON object with 'method', 'params', and 'id' fields. This is a JSON-RPC-inspired pattern rather than a traditional REST API. The request body structure is: { method: 'getLeads', params: { where: {}, limit: 500, offset: 0 }, id: 'your-request-id' }. The 'id' can be any string you choose β it is echoed back in the response and is useful for correlating requests to responses in async contexts. All responses follow the same structure: { result: { lead: [...] }, error: null, id: 'your-request-id' }. The result key varies by method (lead for getLeads, opportunity for getOpportunities). Always check the error field β SharpSpring returns 200 responses even when the operation fails, putting error information in the error field of the response body. The base URL for all API calls is https://api.sharpspring.com/pubapi/v1/ with accountID and secretKey as query parameters.
1// sharpspring-client.js β SharpSpring REST API client for Replit2const ACCOUNT_ID = process.env.SHARPSPRING_ACCOUNT_ID;3const SECRET_KEY = process.env.SHARPSPRING_SECRET_KEY;4const BASE_URL = `https://api.sharpspring.com/pubapi/v1/?accountID=${ACCOUNT_ID}&secretKey=${SECRET_KEY}`;56let requestId = 1;78async function sharpSpringRequest(method, params = {}) {9 const body = {10 method,11 params,12 id: `req_${requestId++}` // Unique request ID13 };1415 const response = await fetch(BASE_URL, {16 method: 'POST',17 headers: { 'Content-Type': 'application/json' },18 body: JSON.stringify(body)19 });2021 if (!response.ok) {22 throw new Error(`HTTP error: ${response.status}`);23 }2425 const data = await response.json();2627 // SharpSpring returns errors in the body even on HTTP 20028 if (data.error) {29 throw new Error(`SharpSpring error: ${JSON.stringify(data.error)}`);30 }3132 return data.result;33}3435// --- LEADS ---3637async function getLeads(where = {}, limit = 100, offset = 0) {38 const result = await sharpSpringRequest('getLeads', { where, limit, offset });39 return result?.lead || [];40}4142async function createLead(leadData) {43 const result = await sharpSpringRequest('createLeads', {44 objects: [leadData] // SharpSpring uses array for batch creates45 });46 return result?.creates?.[0];47}4849async function updateLead(leadId, updates) {50 const result = await sharpSpringRequest('updateLeads', {51 objects: [{ id: leadId, ...updates }]52 });53 return result?.updates?.[0];54}5556// --- OPPORTUNITIES ---5758async function createOpportunity(oppData) {59 const result = await sharpSpringRequest('createOpportunities', {60 objects: [oppData]61 });62 return result?.creates?.[0];63}6465// --- LISTS ---6667async function getLists() {68 const result = await sharpSpringRequest('getActiveLists', {});69 return result?.list || [];70}7172async function addLeadToList(leadId, listId) {73 return sharpSpringRequest('createListMemberships', {74 objects: [{ leadID: leadId, listID: listId }]75 });76}7778// Example: create a lead79(async () => {80 try {81 const lead = await createLead({82 emailAddress: 'contact@example.com',83 firstName: 'Jane',84 lastName: 'Smith',85 companyName: 'Acme Corp',86 phone: '555-123-4567',87 leadScore: 5088 });89 console.log('Lead created:', lead);9091 // Get available lists92 const lists = await getLists();93 console.log(`Available lists: ${lists.length}`);94 lists.slice(0, 3).forEach(l => console.log(` - ${l.name} (ID: ${l.id})`));95 } catch (err) {96 console.error('Error:', err.message);97 }98})();99100module.exports = { sharpSpringRequest, getLeads, createLead, updateLead, createOpportunity, getLists, addLeadToList };Pro tip: SharpSpring's create operations take an 'objects' array even when creating a single record. This design supports batch creation (multiple leads in one request), which is more efficient than individual creates when importing large contact lists.
Expected result: Running the script creates a lead in SharpSpring and lists available active lists. The new lead appears in SharpSpring's CRM under Contacts/Leads.
Manage Leads and Opportunities with Python
Manage Leads and Opportunities with Python
The Python implementation follows the same JSON-RPC-style request pattern as Node.js. Use the requests library (available in most Replit Python environments) to POST to the SharpSpring API endpoint. The SharpSpring endpoint URL is constructed by combining the base URL with the Account ID and Secret Key as query parameters. In Python, use os.environ to pull the credentials from Replit Secrets and format the URL at module initialization time. SharpSpring's field names for leads follow camelCase conventions: emailAddress, firstName, lastName, companyName, phone, leadScore, and any custom fields added to your account. For custom fields, SharpSpring uses internal field names that you can discover by calling the 'getFields' API method or by inspecting field names in the SharpSpring interface. For Flask applications, wrap the SharpSpring functions in route handlers that accept data from your frontend or other services. Error handling should catch both network errors and SharpSpring API errors (returned in the response body with HTTP 200).
1# sharpspring_client.py β SharpSpring API client for Replit (Python)2import os3import requests45ACCOUNT_ID = os.environ['SHARPSPRING_ACCOUNT_ID']6SECRET_KEY = os.environ['SHARPSPRING_SECRET_KEY']7BASE_URL = f'https://api.sharpspring.com/pubapi/v1/?accountID={ACCOUNT_ID}&secretKey={SECRET_KEY}'89_request_counter = 01011def ss_request(method: str, params: dict = None) -> dict:12 """Make a SharpSpring API request."""13 global _request_counter14 _request_counter += 11516 body = {17 'method': method,18 'params': params or {},19 'id': f'req_{_request_counter}'20 }2122 response = requests.post(BASE_URL, json=body)23 response.raise_for_status()24 data = response.json()2526 if data.get('error'):27 raise Exception(f'SharpSpring API error: {data["error"]}')2829 return data.get('result', {})3031def get_leads(where: dict = None, limit: int = 100, offset: int = 0) -> list:32 """Fetch leads with optional filtering."""33 result = ss_request('getLeads', {34 'where': where or {},35 'limit': limit,36 'offset': offset37 })38 return result.get('lead', [])3940def create_lead(email: str, first_name: str, last_name: str,41 company: str = None, phone: str = None, **custom_fields) -> dict:42 """Create a new lead in SharpSpring."""43 lead_data = {44 'emailAddress': email,45 'firstName': first_name,46 'lastName': last_name47 }48 if company: lead_data['companyName'] = company49 if phone: lead_data['phone'] = phone50 lead_data.update(custom_fields)5152 result = ss_request('createLeads', {'objects': [lead_data]})53 return result.get('creates', [{}])[0]5455def update_lead(lead_id: str, updates: dict) -> dict:56 """Update a lead by ID."""57 updates['id'] = lead_id58 result = ss_request('updateLeads', {'objects': [updates]})59 return result.get('updates', [{}])[0]6061def create_opportunity(contact_id: str, name: str, value: float,62 pipeline_stage: str, close_date: str) -> dict:63 """Create an opportunity linked to a contact."""64 result = ss_request('createOpportunities', {65 'objects': [{66 'contactID': contact_id,67 'oppName': name,68 'oppValue': value,69 'pipelineStage': pipeline_stage,70 'closeDate': close_date # YYYY-MM-DD71 }]72 })73 return result.get('creates', [{}])[0]7475def add_to_list(lead_id: str, list_id: str) -> dict:76 """Add a lead to a SharpSpring list."""77 return ss_request('createListMemberships', {78 'objects': [{'leadID': lead_id, 'listID': list_id}]79 })8081if __name__ == '__main__':82 # Create a test lead83 result = create_lead(84 email='test@example.com',85 first_name='Test',86 last_name='User',87 company='Test Company'88 )89 print('Lead created:', result)Pro tip: SharpSpring custom fields can be discovered by calling ss_request('getFields', {'where': {'type': 'lead'}}) β this returns all available lead fields including custom ones with their internal field names that you use in create/update calls.
Expected result: Running the Python script creates a test lead in SharpSpring. The lead appears in the SharpSpring CRM under the Contacts section.
Build an Express Server and Handle SharpSpring Webhooks
Build an Express Server and Handle SharpSpring Webhooks
SharpSpring supports webhook notifications for lead activity events β form submissions, email opens, link clicks, and lead score changes. These webhooks let you react in real time when a lead interacts with your marketing content. To configure webhooks in SharpSpring: go to Settings β API Settings β Event Notifications (or Webhooks). Add your deployed Replit URL as the notification endpoint. SharpSpring will POST JSON event data to this URL whenever the configured events occur. SharpSpring webhook payloads contain event type, the lead's information, and event-specific data (which form was submitted, which email was clicked). The payload format can vary by event type β log the full payload during initial setup to understand its structure. For high-reliability webhook processing, acknowledge the webhook immediately with a 200 response and process the event asynchronously (write to a queue or database, then process in the background). This prevents SharpSpring from retrying due to processing delays. Deploy as Autoscale for most webhook use cases β SharpSpring webhooks are triggered by user behavior, which is inherently sporadic. Autoscale handles the variable load efficiently.
1// sharpspring-server.js β Express server for SharpSpring API proxy and webhooks2const express = require('express');3const { createLead, getLeads, addLeadToList } = require('./sharpspring-client');4const app = express();5app.use(express.json());67// POST /leads β create a lead from external form or service8app.post('/leads', async (req, res) => {9 try {10 const { email, firstName, lastName, company, phone, listId } = req.body;11 if (!email || !firstName || !lastName) {12 return res.status(400).json({ error: 'email, firstName, and lastName are required' });13 }1415 const lead = await createLead({16 emailAddress: email,17 firstName,18 lastName,19 companyName: company,20 phone,21 leadSource: 'API Import'22 });2324 // If a list ID is provided, add the lead to the list25 if (listId && lead?.id) {26 await addLeadToList(lead.id, listId);27 }2829 res.status(201).json({ success: true, leadId: lead?.id });30 } catch (err) {31 console.error('Create lead error:', err.message);32 res.status(500).json({ error: err.message });33 }34});3536// GET /leads β query leads with optional filters37app.get('/leads', async (req, res) => {38 try {39 const where = {};40 if (req.query.email) where.emailAddress = req.query.email;41 const leads = await getLeads(where, parseInt(req.query.limit) || 50);42 res.json(leads);43 } catch (err) {44 res.status(500).json({ error: err.message });45 }46});4748// POST /webhook/sharpspring β receive SharpSpring event notifications49app.post('/webhook/sharpspring', (req, res) => {50 // Acknowledge immediately before processing51 res.status(200).json({ received: true });5253 // Process asynchronously54 setImmediate(() => {55 const events = Array.isArray(req.body) ? req.body : [req.body];56 for (const event of events) {57 const eventType = event.type || event.eventType;58 const lead = event.lead || event.data;59 console.log(`SharpSpring event: ${eventType}`, lead?.emailAddress || '');6061 if (eventType === 'formSubmit' || eventType === 'form_submit') {62 console.log('Form submitted by:', lead?.emailAddress, 'β Form:', event.formName);63 // TODO: trigger additional actions (Slack notify, CRM update, etc.)64 } else if (eventType === 'emailOpen') {65 console.log('Email opened by:', lead?.emailAddress);66 } else if (eventType === 'leadScoreChange') {67 console.log(`Lead score changed: ${event.oldScore} β ${event.newScore}`);68 }69 }70 });71});7273app.listen(3000, '0.0.0.0', () => {74 console.log('SharpSpring integration server running on port 3000');75});Pro tip: SharpSpring webhook payloads are not always consistently structured across event types. Add console.log(JSON.stringify(req.body, null, 2)) in your webhook handler during initial setup to see the exact payload format for each event type you are handling.
Expected result: The Express server starts on port 3000. POST /leads creates a lead in SharpSpring and returns the lead ID. The webhook endpoint accepts SharpSpring event notifications and logs event details.
Common use cases
Lead Capture from External Sources
Sync leads from external sources (paid ads, webinar registrations, partner platforms, custom web forms) into SharpSpring as soon as they are captured. The Replit middleware receives form data or API events, creates a SharpSpring lead record with appropriate tags and list memberships, and initiates the lead nurturing sequence automatically.
Build a webhook server that receives webinar registration data (name, email, company, job title), creates a SharpSpring lead with a 'Webinar Registrant' tag, adds them to the Webinar Nurture list, and returns the SharpSpring lead ID for tracking.
Copy this prompt to try it in Replit
CRM Opportunity Sync
Keep SharpSpring opportunities in sync with an external sales tool or custom application. When a deal is created or updated in your system, the Replit backend updates the corresponding SharpSpring opportunity with the new stage, value, and close date β ensuring marketing and sales have a shared view of the pipeline.
Create an API endpoint that accepts opportunity updates from an external sales system, finds or creates the corresponding SharpSpring opportunity linked to the contact, updates the stage and expected close date, and logs the sync result for auditing.
Copy this prompt to try it in Replit
Agency White-Label Reporting Dashboard
Build a simplified reporting interface for agency clients that pulls SharpSpring campaign and lead data without giving clients direct access to SharpSpring. The Replit API server retrieves lead counts, email performance metrics, and active campaign data β formats it for client consumption, and serves it from a branded dashboard endpoint.
Create a reporting API that queries SharpSpring for the current month's new leads, email campaign open rates, and top lead sources, formats the data into a client-friendly JSON report, and serves it at a password-protected dashboard endpoint.
Copy this prompt to try it in Replit
Troubleshooting
SharpSpring API returns { error: { code: 301, message: 'Invalid application credentials' } }
Cause: SHARPSPRING_ACCOUNT_ID or SHARPSPRING_SECRET_KEY in Replit Secrets is incorrect. SharpSpring returns HTTP 200 with an error in the body for authentication failures β the error is not visible from the HTTP status code alone.
Solution: In SharpSpring Settings β API Settings, verify your Account ID and Secret Key. Copy them again carefully and update the corresponding values in Replit Secrets. Note that SharpSpring's Account ID is numeric β if you copied a different identifier, it will fail.
1// Test credentials with a simple getLeads call2const testUrl = `https://api.sharpspring.com/pubapi/v1/?accountID=${process.env.SHARPSPRING_ACCOUNT_ID}&secretKey=${process.env.SHARPSPRING_SECRET_KEY}`;3fetch(testUrl, { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({ method: 'getLeads', params: { where: {}, limit: 1 }, id: 'test' }) })4 .then(r => r.json())5 .then(d => console.log('Auth test:', d.error ? 'FAIL: ' + JSON.stringify(d.error) : 'OK')); createLeads returns success but lead does not appear in SharpSpring
Cause: SharpSpring may silently deduplicate leads by email address. If the email already exists, SharpSpring may return a success response but not create a new lead, or it may merge the data. SharpSpring also has email validation β invalid or disposable email domains may be rejected silently.
Solution: Check if the email address already exists by calling getLeads with a where: { emailAddress: email } filter before creating. For updates to existing leads, use updateLeads instead of createLeads to ensure changes are applied. Verify the email address is valid and not a disposable domain.
SharpSpring webhooks are not arriving at the Replit endpoint
Cause: The webhook notification URL in SharpSpring points to a development Replit URL that is offline, or webhook notifications are not enabled in SharpSpring Settings, or the URL format is incorrect.
Solution: Deploy your Replit app and use the deployed HTTPS URL in SharpSpring's webhook settings. Verify Event Notifications are enabled in SharpSpring Settings β API Settings β Event Notifications. Check that the URL is accessible by making a manual POST to it with a test payload.
List membership creation fails with 'List not found' or 'Invalid listID'
Cause: The list ID used in addLeadToList does not match any active list in SharpSpring. List IDs are account-specific and change if lists are deleted and recreated.
Solution: Call the getLists function (getActiveLists method) to retrieve all current lists and their IDs. Store the correct list ID in Replit Secrets or a configuration file rather than hardcoding it.
1// Fetch and print all active lists and their IDs2sharpSpringRequest('getActiveLists', {})3 .then(r => (r.list || []).forEach(l => console.log(`List ${l.id}: ${l.name}`)));Best practices
- Store SHARPSPRING_ACCOUNT_ID and SHARPSPRING_SECRET_KEY in Replit Secrets (lock icon π) β these credentials authenticate all API operations including reading sensitive lead data
- Build the SharpSpring endpoint URL from environment variables in code rather than storing the full URL with embedded credentials in Secrets or source files
- Check the 'error' field in every SharpSpring response β the API returns HTTP 200 for both success and failure, so HTTP status codes alone are not sufficient for error detection
- Use SharpSpring's batch create/update pattern (objects array) when importing multiple leads to reduce API round trips and avoid rate limiting
- Fetch list IDs and pipeline stage names dynamically at startup rather than hardcoding them β these values are account-specific and change when lists or stages are modified
- Deploy webhook receivers as Autoscale or Reserved VM β development Repls go offline and cannot receive SharpSpring event notifications reliably
- Log raw webhook payloads during development to understand SharpSpring's event format before building your event handlers
- Implement deduplication logic for lead creation β check if an email already exists before creating a new lead to avoid partial updates or silent merges
Alternatives
HubSpot has a larger user base, more extensive API documentation, and a broader app marketplace than SharpSpring, making it better for teams that need a well-supported marketing automation platform.
Keap (formerly Infusionsoft) targets similar SMB and small agency customers as SharpSpring with combined CRM and email automation, worth comparing for non-agency use cases.
Autopilot focuses on visual customer journey automation with a simpler API than SharpSpring, making it a better fit if visual workflow design is the primary requirement.
Frequently asked questions
How do I find my SharpSpring API Account ID and Secret Key?
Log into SharpSpring and go to the gear icon (Settings) β API Settings. Your Account ID and Secret Key are shown on that page. If you do not see an API Settings option, your SharpSpring plan may not include API access β contact SharpSpring support. Store both values in Replit Secrets as SHARPSPRING_ACCOUNT_ID and SHARPSPRING_SECRET_KEY.
Why does SharpSpring use a POST body authentication style instead of headers?
SharpSpring's API design predates modern REST API conventions β it uses a JSON-RPC-inspired style where all operations POST to a single endpoint with the method as a parameter. This means authentication credentials go in the URL query string (accountID and secretKey) rather than in HTTP Authorization headers. It is unusual but consistent throughout the entire SharpSpring API.
Can I use SharpSpring with Replit for free?
SharpSpring does not have a free tier β it is a paid platform aimed at agencies and SMBs. Plans start at a per-account price that includes API access. SharpSpring does not publish pricing publicly; you need to contact their sales team for a quote. There is no free trial API access without an active account.
How do I trigger a SharpSpring automation workflow from Replit?
SharpSpring automation workflows (called 'Automations' or 'Workflows') are typically triggered by lead activities β form submissions, list membership changes, lead score thresholds, or email interactions. From Replit, you trigger them indirectly by: adding a lead to a specific list (createListMemberships), updating the lead's score or custom fields to cross a threshold, or using the SharpSpring API to fire a specific event. Direct workflow triggering via API is not currently supported β you trigger the conditions the workflow listens for.
What deployment type should I use for a SharpSpring webhook receiver on Replit?
Use Autoscale deployment for SharpSpring webhook receivers. SharpSpring events (form submissions, email opens) are infrequent and bursty β Autoscale handles this pattern efficiently by scaling up when events arrive and scaling to zero when idle. The 1-3 second cold start on Autoscale is well within SharpSpring's webhook timeout window. Deployed URL: https://yourapp.replit.app/webhook/sharpspring.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation