# How to Integrate Replit with Autopilot

- Tool: Replit
- Difficulty: Intermediate
- Time required: 25 minutes
- Last updated: March 2026

## TL;DR

To integrate Replit with Autopilot, generate an API key from your Autopilot account, store it in Replit Secrets (lock icon 🔒), and call the Autopilot REST API from your server-side Python or Node.js code to manage contacts, trigger journey enrollments, and sync smart segments. Use Autoscale deployment for web apps that feed contacts into marketing journeys.

## Why Connect Replit to Autopilot?

Autopilot's strength is its visual journey builder — marketers can map out multi-step customer experiences across email, SMS, in-app messages, and direct mail without writing code. The REST API unlocks the other half: letting your application feed real behavioral data into those journeys automatically. When a user upgrades their plan, completes a workflow, or crosses a usage threshold in your Replit app, you can immediately update their Autopilot contact record and enroll them in the matching nurture journey.

The most powerful pattern is using Replit as the data bridge between your product and Autopilot. Your app tracks the events that matter — first login, feature adoption, inactivity — and translates them into Autopilot contact properties, segment memberships, and journey triggers. Marketers then design the journeys in Autopilot's visual canvas without needing to ask engineers to change code every time a new campaign launches.

Replit's Secrets system (lock icon 🔒 in the sidebar) keeps your Autopilot API key encrypted and inaccessible from Git history or client-side code. Because the API key grants full read/write access to your entire contact database and all journeys, it must only be used in server-side code. Never expose it in a frontend React or Vue component.

## Before you start

- A Replit account with a Python or Node.js project created
- An Autopilot account (trial or paid) with at least one journey created
- Basic familiarity with REST APIs and HTTP headers
- Node.js 18+ or Python 3.10+ (both available on Replit by default)
- Your Autopilot journey IDs and any custom contact field names you plan to use

## Step-by-step guide

### 1. Generate an Autopilot API Key and Store It in Replit Secrets

Log in to your Autopilot account and click the gear icon in the top-right corner to open Settings. Navigate to 'Autopilot API' in the left sidebar. On the API page, your API key is displayed — it is a long alphanumeric string. If you do not see one, click 'Generate API Key'. Copy the key immediately, as Autopilot may not display it again after you navigate away.

Next, open your Replit project and click the lock icon 🔒 in the left sidebar to open the Secrets pane. Click 'Add a new secret' and enter AUTOPILOT_API_KEY as the key and your copied API key as the value. Click 'Add Secret' to save it. Replit encrypts this value with AES-256 encryption and injects it as an environment variable at runtime — it will never appear in your file tree or Git history.

You should also note your journey IDs from the Autopilot journey list (visible in the URL when you open a journey: /journeys/{journey_id}). Store any journey IDs you plan to use as additional secrets like AUTOPILOT_ONBOARDING_JOURNEY_ID for cleaner code. In Python, access the key with os.environ['AUTOPILOT_API_KEY']; in Node.js, use process.env.AUTOPILOT_API_KEY.

**Expected result:** The AUTOPILOT_API_KEY Secret appears in the Replit Secrets pane and is accessible as an environment variable in your code.

### 2. Add and Update Contacts with Custom Fields in Python

The Autopilot REST API base URL is https://api2.autopilothq.com/v1. All requests require the autopilotapikey header set to your API key and Content-Type: application/json for POST/PATCH requests. The contact object uses a specific structure where custom fields are prefixed with 'string--', 'integer--', 'float--', or 'boolean--' followed by the field name — this prefix tells Autopilot the data type.

To add or update a contact, use POST /contact with the contact data. If the email already exists, Autopilot updates the existing record (upsert behavior). You can also trigger a journey enrollment in the same request by including the trigger field. The API is synchronous — a successful 200 response means the contact was created or updated.

The Python code below shows how to add a contact with custom fields, check if a contact exists, and update individual fields without overwriting the entire record. The requests library is sufficient — no third-party Autopilot SDK is needed.

```
import os
import requests

API_KEY = os.environ["AUTOPILOT_API_KEY"]
BASE_URL = "https://api2.autopilothq.com/v1"

HEADERS = {
    "autopilotapikey": API_KEY,
    "Content-Type": "application/json"
}

def add_or_update_contact(email: str, first_name: str = "", last_name: str = "",
                          custom_fields: dict = None) -> dict:
    """Add a new contact or update an existing one by email."""
    contact_data = {
        "contact": {
            "Email": email,
            "FirstName": first_name,
            "LastName": last_name
        }
    }
    # Add custom fields with correct type prefix
    if custom_fields:
        for key, value in custom_fields.items():
            if isinstance(value, bool):
                contact_data["contact"][f"boolean--{key}"] = value
            elif isinstance(value, int):
                contact_data["contact"][f"integer--{key}"] = value
            elif isinstance(value, float):
                contact_data["contact"][f"float--{key}"] = value
            else:
                contact_data["contact"][f"string--{key}"] = str(value)

    response = requests.post(
        f"{BASE_URL}/contact",
        json=contact_data,
        headers=HEADERS
    )
    response.raise_for_status()
    return response.json()

def get_contact(email: str) -> dict:
    """Retrieve a contact record by email address."""
    import urllib.parse
    encoded_email = urllib.parse.quote(email)
    response = requests.get(
        f"{BASE_URL}/contact/{encoded_email}",
        headers=HEADERS
    )
    if response.status_code == 404:
        return None
    response.raise_for_status()
    return response.json()

def enroll_in_journey(journey_id: str, contact_email: str) -> dict:
    """Trigger a contact's enrollment into a specific journey."""
    data = {"contact": {"Email": contact_email}}
    response = requests.post(
        f"{BASE_URL}/journey/{journey_id}/contact",
        json=data,
        headers=HEADERS
    )
    response.raise_for_status()
    return response.json()

def add_to_list(list_id: str, contact_email: str) -> dict:
    """Add a contact to a smart segment/list."""
    data = {"contact": {"Email": contact_email}}
    response = requests.post(
        f"{BASE_URL}/list/{list_id}/subscriber",
        json=data,
        headers=HEADERS
    )
    response.raise_for_status()
    return response.json()

# Example usage
if __name__ == "__main__":
    result = add_or_update_contact(
        email="user@example.com",
        first_name="Jane",
        last_name="Doe",
        custom_fields={
            "plan_type": "pro",
            "signup_source": "organic",
            "trial_days_remaining": 14
        }
    )
    print(f"Contact created/updated: {result}")

    journey_id = os.environ.get("AUTOPILOT_ONBOARDING_JOURNEY_ID", "")
    if journey_id:
        enroll_in_journey(journey_id, "user@example.com")
        print("Enrolled in onboarding journey")
```

**Expected result:** Running the script adds a test contact to Autopilot with custom fields and logs a success response. The contact should appear in your Autopilot contacts list within a few seconds.

### 3. Build a Node.js Express Server for Journey Automation

For Node.js applications, the Autopilot API is called with the node-fetch or axios library — no official SDK exists. The Express server below exposes POST /contacts/register for adding new contacts and POST /contacts/event for updating behavioral properties that trigger journey conditions.

The server demonstrates the most common integration pattern: an app event triggers an Autopilot contact update, which in turn fires a journey action. For example, when a user upgrades from a free to a paid plan, the app sends a PATCH to update the 'plan_type' custom field, and an Autopilot journey condition on that field automatically sends a welcome-to-paid email without any further code.

The 'unsubscribe all' endpoint is also important for compliance — if a user requests deletion or opts out of all marketing, you can immediately unsubscribe them from all journeys and lists by setting OptOut to true on the contact record. Deploy this server on Replit with Autoscale for reliable handling of registration-driven journey enrollments. Autoscale ensures the server is available when users sign up at unexpected times without paying for always-on resources.

```
const express = require('express');
const axios = require('axios');

const app = express();
app.use(express.json());

const API_KEY = process.env.AUTOPILOT_API_KEY;
const BASE_URL = 'https://api2.autopilothq.com/v1';
const ONBOARDING_JOURNEY_ID = process.env.AUTOPILOT_ONBOARDING_JOURNEY_ID;

const autopilot = axios.create({
  baseURL: BASE_URL,
  headers: {
    'autopilotapikey': API_KEY,
    'Content-Type': 'application/json'
  }
});

// Add or update a contact (upsert by email)
app.post('/contacts/register', async (req, res) => {
  const { email, firstName, lastName, plan, source } = req.body;
  if (!email) return res.status(400).json({ error: 'email is required' });

  try {
    const payload = {
      contact: {
        Email: email,
        FirstName: firstName || '',
        LastName: lastName || '',
        'string--plan_type': plan || 'free',
        'string--signup_source': source || 'direct',
        'integer--signup_timestamp': Math.floor(Date.now() / 1000)
      }
    };

    const { data } = await autopilot.post('/contact', payload);

    // Enroll in onboarding journey if configured
    if (ONBOARDING_JOURNEY_ID) {
      await autopilot.post(`/journey/${ONBOARDING_JOURNEY_ID}/contact`, {
        contact: { Email: email }
      });
    }

    res.json({ success: true, contactId: data.contact_id });
  } catch (err) {
    console.error('Autopilot error:', err.response?.data || err.message);
    res.status(500).json({ error: err.message });
  }
});

// Update a contact property (e.g., plan upgrade event)
app.post('/contacts/event', async (req, res) => {
  const { email, event, properties } = req.body;
  if (!email || !event) return res.status(400).json({ error: 'email and event required' });

  try {
    // Build contact update with typed custom fields
    const contactFields = { Email: email };
    if (properties) {
      for (const [key, value] of Object.entries(properties)) {
        const prefix = typeof value === 'number' ? 'integer--' : 'string--';
        contactFields[`${prefix}${key}`] = value;
      }
    }
    // Add the event name as a custom field for journey triggering
    contactFields['string--last_event'] = event;
    contactFields['integer--last_event_timestamp'] = Math.floor(Date.now() / 1000);

    await autopilot.post('/contact', { contact: contactFields });
    res.json({ success: true });
  } catch (err) {
    console.error('Event update error:', err.response?.data || err.message);
    res.status(500).json({ error: err.message });
  }
});

// Unsubscribe a contact from all marketing
app.post('/contacts/unsubscribe', async (req, res) => {
  const { email } = req.body;
  if (!email) return res.status(400).json({ error: 'email is required' });

  try {
    await autopilot.post('/contact', {
      contact: { Email: email, unsubscribed: true }
    });
    res.json({ success: true });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

app.listen(3000, '0.0.0.0', () => {
  console.log('Autopilot integration server running on port 3000');
});
```

**Expected result:** The Express server starts on port 3000. A POST to /contacts/register with a valid email returns a contactId and the contact appears in Autopilot's contact list.

### 4. Receive Autopilot Webhook Events and Deploy

Autopilot can send webhook notifications to your Replit server when contacts reach specific points in a journey — for example, when a contact clicks a link, replies to an email, or completes a journey. Setting up webhooks allows your app to react to marketing engagement data in real time without polling the API.

To configure webhooks in Autopilot, open a journey in the visual canvas and add a 'Notify via webhook' action from the actions panel. Enter your deployed Replit URL as the webhook endpoint (e.g., https://your-app.replit.app/autopilot/webhook). Autopilot sends a POST request with a JSON body containing the contact's details and the journey action context.

Webhooks require a deployed URL — not a development session URL. In Replit, click 'Deploy' and choose Autoscale for apps that serve a web frontend alongside webhook handling, or Reserved VM if webhook processing is the primary workload and you need guaranteed sub-second response times. After deployment, copy the stable URL from the Deployments panel and register it in Autopilot. Test the webhook by manually triggering a journey action for a test contact.

```
from flask import Flask, request, jsonify
import os
import json

app = Flask(__name__)

@app.route('/autopilot/webhook', methods=['POST'])
def autopilot_webhook():
    try:
        data = request.get_json(force=True)
        if not data:
            return jsonify({'error': 'No JSON body'}), 400

        # Extract contact information from the webhook payload
        contact = data.get('contact', {})
        email = contact.get('Email', '')
        first_name = contact.get('FirstName', '')
        journey_name = data.get('journey_name', 'unknown')
        action_type = data.get('type', 'unknown')

        print(f"Autopilot event: {action_type} in journey '{journey_name}' for {email}")

        # Process different event types
        if action_type == 'journey_completed':
            # Handle journey completion — e.g., update CRM, notify sales team
            print(f"Contact {email} completed journey: {journey_name}")
            # update_crm_lead_score(email, points=50)

        elif action_type == 'email_clicked':
            link_url = data.get('link_url', '')
            print(f"Contact {email} clicked: {link_url}")
            # track_engagement_event(email, 'email_click', link_url)

        elif action_type == 'unsubscribed':
            print(f"Contact {email} unsubscribed")
            # mark_user_unsubscribed(email)

        return jsonify({'status': 'received', 'email': email}), 200

    except Exception as e:
        print(f"Webhook error: {e}")
        return jsonify({'error': str(e)}), 500

@app.route('/health', methods=['GET'])
def health():
    return jsonify({'status': 'ok'}), 200

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=3000)
```

**Expected result:** Your deployed Replit server logs incoming Autopilot journey events. The health endpoint at /health returns a 200 response to confirm the server is live.

## Best practices

- Store your Autopilot API key in Replit Secrets (lock icon 🔒) — never hardcode it in source files or include it in client-side JavaScript.
- Define all custom contact fields in Autopilot Settings before using them in the API — fields not defined in the account are silently dropped and will cause data loss.
- Use upsert behavior (POST /contact) rather than separate create/update logic — Autopilot automatically updates existing contacts and creates new ones based on email address.
- Always include the correct type prefix (string--, integer--, boolean--, float--) on custom field names or the API will ignore the fields without returning an error.
- Publish journeys in Autopilot before attempting to enroll contacts — Draft journeys return 404 errors and failed enrollments are not retried automatically.
- Deploy your Replit app before registering webhook URLs — use your stable replit.app deployment URL, not the temporary development URL.
- Implement a token parameter on your webhook endpoint URL and validate it from Replit Secrets, since Autopilot does not sign webhook payloads.
- Use Autoscale deployment for apps that enroll contacts during peak signup times, and Reserved VM for dedicated webhook processors that require zero cold-start latency.

## Use cases

### Sync New App Signups to Autopilot Journeys

When a user registers in your Replit web application, automatically add them as a contact in Autopilot with custom fields reflecting their plan, signup source, and intent. Enroll them in an onboarding journey immediately so they receive the right sequence of welcome emails and activation nudges without any manual effort from your marketing team.

Prompt example:

```
Build a Flask endpoint that receives new user registration data, adds the user as an Autopilot contact with custom fields for plan type and signup date using AUTOPILOT_API_KEY from Replit Secrets, and enrolls them in the onboarding journey by journey ID.
```

### Behavioral Segment Updates Based on Product Events

As users interact with your product — hitting usage milestones, going dormant, or reaching upgrade triggers — your Replit backend updates their Autopilot contact properties and moves them between smart segments. Autopilot journeys respond to segment membership changes automatically, firing the right message at the right moment in the customer lifecycle.

Prompt example:

```
Write a Node.js script that queries your database for users who have been inactive for 14 days, updates their Autopilot contact with a 'last_active_days' custom field, and adds them to a re-engagement segment via the Autopilot API.
```

### Lead Scoring Webhook Receiver

Deploy a Replit server that receives Autopilot webhook events when contacts reach journey milestones — such as opening three emails or clicking a pricing page link. Your server writes the lead score back to your CRM or database, allowing your sales team to see which Autopilot-nurtured leads are ready for outreach without switching between tools.

Prompt example:

```
Create an Express server with a POST /autopilot/webhook endpoint that receives Autopilot journey completion events, extracts the contact email and journey name, and updates a lead score field in a PostgreSQL database.
```

## Troubleshooting

### API returns 401 Unauthorized on every request

Cause: The autopilotapikey header is missing, misspelled, or contains extra whitespace. Autopilot uses a custom header name — unlike most APIs that use Authorization: Bearer, Autopilot's header is literally autopilotapikey (all lowercase).

Solution: Verify the header name is exactly 'autopilotapikey' with no capital letters or spaces. In Replit Secrets, confirm the key value has no leading or trailing spaces. Print the first and last 4 characters of the key to verify it loaded correctly without exposing the full key.

```
# Python: verify header is set correctly
headers = {
    'autopilotapikey': os.environ['AUTOPILOT_API_KEY'].strip(),
    'Content-Type': 'application/json'
}
# Sanity check (never log the full key)
print(f"Key starts: {os.environ['AUTOPILOT_API_KEY'][:4]}...")
```

### Contact is created but custom fields are not saved

Cause: Custom field names must include the data type prefix (string--, integer--, float--, boolean--) and must match the field names defined in your Autopilot account exactly, including case. Fields not defined in Autopilot settings are silently dropped.

Solution: Open Autopilot Settings > Custom Contact Fields and verify your field names. In the API payload, prepend the correct type prefix: 'string--plan_type', 'integer--trial_days', 'boolean--is_premium'. Mismatched case (e.g., 'string--Plan_Type' vs 'string--plan_type') will cause fields to be ignored silently.

```
# Correct custom field format
contact_data = {
    "contact": {
        "Email": "user@example.com",
        "string--plan_type": "pro",       # string prefix
        "integer--login_count": 5,         # integer prefix
        "boolean--email_verified": True    # boolean prefix
    }
}
```

### Journey enrollment returns 404 Not Found

Cause: The journey ID in the URL is incorrect or the journey is in Draft status. Autopilot only allows enrollments into Published journeys. Draft journeys return a 404 when you try to enroll contacts.

Solution: In Autopilot, open the journey and click 'Publish' to make it active. Copy the journey ID from the URL (/journeys/{id}) and update your AUTOPILOT_ONBOARDING_JOURNEY_ID Secret in Replit. Journey IDs are case-sensitive alphanumeric strings.

### Webhook events are not being received by the Replit server

Cause: The webhook URL is pointing to a development Replit session URL that is only active when the IDE is open. Development URLs stop serving traffic when the Replit session closes.

Solution: Deploy your Replit app using the 'Deploy' button to get a permanent URL at https://your-app.replit.app. Update the webhook URL in the Autopilot journey canvas with this deployed URL. Test the webhook using Autopilot's built-in send-test feature after saving the webhook action.

## Frequently asked questions

### How do I store my Autopilot API key in Replit?

Click the lock icon 🔒 in the left sidebar of your Replit project to open the Secrets pane. Add a new secret with key AUTOPILOT_API_KEY and paste your Autopilot API key as the value. Access it in Python with os.environ['AUTOPILOT_API_KEY'] or in Node.js with process.env.AUTOPILOT_API_KEY. Never put the key directly in your source code.

### Does Replit work with Autopilot on the free tier?

Yes. The Autopilot API is accessible from any Replit project including free tier. Outbound API calls to Autopilot work without restrictions. However, you will need Replit's paid plan for always-on deployments required for reliable webhook reception, since free tier Replit projects go to sleep when inactive.

### Why are my Autopilot custom fields not saving?

Custom fields must include a data type prefix in the field name: 'string--', 'integer--', 'float--', or 'boolean--'. The field must also be defined in your Autopilot account under Settings > Custom Contact Fields. Fields sent to the API that are not defined in the account are silently ignored without any error response.

### Can I enroll contacts in multiple Autopilot journeys from Replit?

Yes. Make a separate POST request to /journey/{journey_id}/contact for each journey you want to enroll the contact in. Each enrollment is independent. Make sure each journey is Published (not Draft) or the enrollment will return a 404 error.

### How do I connect Replit to Autopilot for real-time events?

Add a 'Notify via webhook' action in your Autopilot journey canvas, pointing to your deployed Replit server URL. The server must be deployed (not just running in development) to have a stable URL. Autopilot sends a JSON POST to your endpoint whenever a contact triggers that action in the journey.

---

Source: https://www.rapidevelopers.com/replit-integration/autopilot
© RapidDev — https://www.rapidevelopers.com/replit-integration/autopilot
