Integrate Ortto (formerly Autopilot) with Bolt.new using Ortto's REST API through Next.js API routes. Create and update contacts, trigger custom activities, and manage journeys using a simple API key in the Authorization header. Ortto rebranded from Autopilot in 2022 — some documentation still references the old name, but the API is unified under api.ap3api.com. Free trial available; paid plans start at $99/month.
Connect Your Bolt.new App to Ortto Marketing Automation
Ortto (formerly Autopilot) is a marketing automation platform designed around visual customer journey mapping. While HubSpot and Marketo focus on broad CRM functionality, Ortto's core value proposition is the visual journey canvas — a drag-and-drop flow chart that shows exactly how contacts move through your marketing sequences based on their actions. When a user signs up, upgrades, or goes 30 days without logging in, an Ortto journey can automatically send a targeted email, add them to a Slack notification, or trigger any other automation.
The key integration concept is the Custom Activity. A Custom Activity is an event you define in Ortto that represents a meaningful user action in your Bolt.new app: 'User Completed Onboarding', 'Subscription Upgraded', 'Feature Used', 'Trial Expired'. When your app fires this activity for a contact, Ortto journeys that listen for that activity trigger automatically. This is the primary way your Bolt.new app communicates intent and behavior to Ortto's automation engine.
Ortto's API is straightforward: API key authentication (key in the Authorization header), JSON request bodies, and a relatively small set of endpoints for the most common operations. The main endpoints are contact creation/update (upsert by email), custom activity tracking, and contact list management. All calls go through Next.js API routes to keep your API key server-side. Ortto rebranded from Autopilot in 2022 — if you find documentation or forum posts referencing Autopilot's API, the principles are the same but the base URL is now api.ap3api.com and some endpoint paths have changed.
Integration method
Ortto integrates with Bolt.new through Next.js API routes that proxy requests to Ortto's REST API at api.ap3api.com, keeping the API key server-side. Contact creation and activity tracking use simple JSON POST requests with an Authorization header. Custom activities trigger Ortto journeys and automations when users complete actions in your Bolt.new app. All outbound API calls work in Bolt's WebContainer preview; incoming webhooks from Ortto require a deployed URL.
Prerequisites
- An Ortto account — free trial at https://ortto.com (no credit card required for 14 days)
- Your Ortto API key — found in Ortto Settings → API Keys → New API Key
- At least one Custom Activity defined in Ortto — go to Data Sources → Custom Activities → Add Activity
- A Bolt.new account with a Next.js project open
- Basic familiarity with Next.js API routes
Step-by-step guide
Get Your Ortto API Key and Understand the API Structure
Get Your Ortto API Key and Understand the API Structure
Ortto uses simple API key authentication — all requests include your key in the Authorization header as 'Bearer {apiKey}'. No OAuth flows, no token expiry, no scopes to configure. To get your API key, log into Ortto, click your account name in the top-left corner, select Settings, then navigate to the API section (or search for 'API' in Settings). Click 'New API Key', give it a descriptive name like 'Bolt.new App', and copy the generated key. Store it immediately since Ortto may not let you see the full key again after initial creation. Ortto's API base URL is https://api.ap3api.com/v1/ for the current version. If you find older documentation referencing Autopilot's API (api.autopilothq.com), those endpoints have been migrated to the new domain with some path changes. The primary endpoints for most Bolt.new integrations are: person/merge for creating or updating contacts, activity/create for firing custom activities, and person/get for fetching contact data. One important concept in Ortto: contacts are identified by email address. When you call person/merge, Ortto checks if a contact with that email already exists and updates them, or creates a new one. This upsert behavior means you can safely call the merge endpoint for both new signups and existing user profile updates — Ortto handles deduplication automatically. Custom fields are defined in Ortto's interface (Data → Custom Fields) and then referenced in your API calls using their field ID. Ortto uses a workspace-based data model. All API calls are scoped to the workspace associated with your API key. Most single-product companies have one workspace; agencies may have multiple. The workspace ID does not need to be passed in API calls — it is determined by the API key.
1# .env — add to project root in Bolt2# Found at: Ortto → Settings → API Keys → New API Key3ORTTO_API_KEY=your_ortto_api_key_here45# Ortto API base URL6# Note: formerly api.autopilothq.com (legacy Autopilot)7ORTTO_API_BASE=https://api.ap3api.com/v1Pro tip: Ortto API keys have configurable permissions. When creating your API key, select the permissions your integration needs: Contacts (read/write), Activities (write), and Lists (read/write) cover most use cases. Limit permissions to only what your Bolt.new app requires — this reduces the impact if the key is ever exposed.
Expected result: Your Ortto API key is saved in .env. You have noted the API base URL and are ready to build the contact creation utility.
Create Contacts with Ortto's Person Merge API
Create Contacts with Ortto's Person Merge API
The person/merge endpoint is the foundation of the Ortto integration — it creates new contacts or updates existing ones by email, keeping your Ortto contact database in sync with your Bolt.new app's user data. This endpoint handles deduplication automatically, making it safe to call whenever user data changes without worrying about creating duplicates. The request body for person/merge is a JSON object with a fields property containing the contact's data. The email field is required and used for deduplication. Other standard fields include first_name, last_name, phone, company, and website. Custom fields you have defined in Ortto are referenced by their field ID in the custom_fields property. The response includes the contact's Ortto ID (person_id) which you can store in your Supabase user table for future reference. Create a Next.js API route that handles contact upserts. The route accepts your app's user data (from form submissions, sign-up events, or profile updates), formats it for Ortto's API, and calls person/merge. Since this is an outbound HTTP call from your server to Ortto's servers, it works in Bolt's WebContainer development preview — no deployment required for testing. For a sign-up flow integration: call this API route after successful user creation in Supabase. Pass the user's email, name, plan type (if applicable), and any other relevant attributes you want to track in Ortto. Ortto will create the contact and make them available for journey enrollment, email targeting, and segmentation. Add the Ortto person_id to your Supabase users table as an ortto_id column for fast lookups when firing activities for existing users.
Build an Ortto contact management module in Next.js. Create a utility at lib/ortto-client.ts with an upsertContact function that accepts email, firstName, lastName, and optional customFields object. POST to https://api.ap3api.com/v1/person/merge with Authorization: Bearer ${ORTTO_API_KEY} and the fields formatted as Ortto expects. Create API routes: POST /api/ortto/contacts that calls upsertContact, and GET /api/ortto/contacts/[email] that fetches a contact by email using the person/get endpoint. Test by creating a contact with your own email using the Bolt preview.
Paste this in Bolt.new chat
1// lib/ortto-client.ts2const ORTTO_BASE = process.env.ORTTO_API_BASE || 'https://api.ap3api.com/v1';34async function orttoRequest<T>(5 endpoint: string,6 body: unknown,7 method: 'POST' | 'GET' = 'POST'8): Promise<T> {9 const apiKey = process.env.ORTTO_API_KEY;10 if (!apiKey) throw new Error('ORTTO_API_KEY not configured');1112 const res = await fetch(`${ORTTO_BASE}${endpoint}`, {13 method,14 headers: {15 Authorization: `Bearer ${apiKey}`,16 'Content-Type': 'application/json',17 },18 body: method === 'POST' ? JSON.stringify(body) : undefined,19 });2021 if (!res.ok) {22 const error = await res.json().catch(() => ({ message: res.statusText }));23 throw new Error(`Ortto API error ${res.status}: ${JSON.stringify(error)}`);24 }2526 return res.json();27}2829export interface OrttoContact {30 email: string;31 firstName?: string;32 lastName?: string;33 phone?: string;34 company?: string;35 customFields?: Record<string, string | number | boolean>;36}3738export async function upsertContact(contact: OrttoContact) {39 const body = {40 fields: {41 str::email: contact.email,42 str::first: contact.firstName || '',43 str::last: contact.lastName || '',44 str::phone: contact.phone || '',45 str::company: contact.company || '',46 ...contact.customFields,47 },48 merge_by: ['str::email'], // Deduplicate by email49 find_strategy: 0, // 0 = upsert50 };5152 return orttoRequest<{ people: Array<{ id: string; fields: Record<string, unknown> }> }>(53 '/person/merge',54 body55 );56}5758export async function getContact(email: string) {59 const body = {60 filter: {61 type: 'and',62 filters: [{63 field: { field_name: 'str::email', data_type: 'str' },64 type: 'exact',65 value: email,66 }],67 },68 fields: ['str::email', 'str::first', 'str::last'],69 limit: 1,70 };7172 return orttoRequest<{ contacts: Array<Record<string, unknown>> }>('/person/get', body);73}7475// app/api/ortto/contacts/route.ts76import { NextResponse } from 'next/server';77import { upsertContact } from '@/lib/ortto-client';7879export async function POST(request: Request) {80 try {81 const { email, firstName, lastName, company, customFields } = await request.json();8283 if (!email) {84 return NextResponse.json({ error: 'Email is required' }, { status: 400 });85 }8687 const result = await upsertContact({ email, firstName, lastName, company, customFields });88 const person = result.people?.[0];8990 return NextResponse.json({91 success: true,92 personId: person?.id,93 });94 } catch (error) {95 const message = error instanceof Error ? error.message : 'Failed to upsert contact';96 return NextResponse.json({ error: message }, { status: 500 });97 }98}Pro tip: Ortto field names use a type prefix format: str:: for string fields, int:: for integer fields, dbl:: for decimal, bol:: for boolean, and dat:: for dates. Custom fields you create in Ortto get IDs like str::cm:your-field-name. Always check the exact field IDs in Ortto's Data → Fields settings before using them in your API calls.
Expected result: The /api/ortto/contacts endpoint creates or updates a contact in Ortto. Verify by checking Ortto's People section — the contact should appear with the data you sent from the Bolt preview.
Fire Custom Activities to Trigger Ortto Journeys
Fire Custom Activities to Trigger Ortto Journeys
Custom Activities are the most powerful part of the Ortto integration. Where contact creation adds a person to Ortto, activities represent things they do — and Ortto journeys react to these activities automatically. An activity could be 'User Completed Onboarding', 'Subscription Upgraded to Pro', 'Feature X Used for the First Time', or 'Invoice Paid'. When your Bolt.new app fires an activity, any Ortto journey that starts with 'When contact does activity X' runs for that contact. Before you can fire an activity from the API, you need to define it in Ortto's interface. Go to Ortto → Data → Activities → New Activity. Give it a name and optionally define attributes (data that can be attached to each activity instance). For example, a 'Plan Upgraded' activity might have attributes: previous_plan (string), new_plan (string), and mrr_cents (integer). These attributes are available in Ortto journeys for personalization and conditional branching. The activity creation API call is POST /v1/activity/create. The body includes the activity_id (from Ortto's activity settings), the contact identified by email, and optionally activity attributes. The activity_id is the machine-readable key you set when creating the activity in Ortto — typically snake_case like 'user_onboarding_completed' or 'subscription_upgraded'. For your Bolt.new app, fire activities at meaningful points in the user lifecycle: after onboarding completion, when a subscription upgrades, when a key feature is used for the first time, when a user invites a team member. Each activity gives Ortto's journey engine the signals it needs to send the right email at the right time. Build a centralized track() function in your Ortto client utility that any part of your app can call with a contact email, activity name, and optional attributes — this keeps the tracking logic consistent across the app.
Add custom activity tracking to the Ortto integration. Update lib/ortto-client.ts with a trackActivity function that accepts personEmail, activityId, and optional attributes object. POST to /v1/activity/create with the contact identified by email and the activity data. Create API routes: POST /api/ortto/activities that calls trackActivity. Build a test page at /admin/ortto-test with a form showing: email input, activity dropdown (with hardcoded options: user_registered, onboarding_completed, subscription_upgraded, feature_used), and key-value pairs for up to 3 activity attributes. On submit, call the activities route and show the response. This makes it easy to test activities without code changes.
Paste this in Bolt.new chat
1// Add to lib/ortto-client.ts23export async function trackActivity(4 personEmail: string,5 activityId: string,6 attributes?: Record<string, string | number | boolean>7) {8 const body = {9 activities: [10 {11 activity_id: activityId,12 // Identify the contact by email13 attributes: {14 'str::email': personEmail,15 ...attributes,16 },17 },18 ],19 };2021 return orttoRequest<{ count: number }>('/activity/create', body);22}2324// app/api/ortto/activities/route.ts25import { NextResponse } from 'next/server';26import { trackActivity, upsertContact } from '@/lib/ortto-client';2728export async function POST(request: Request) {29 try {30 const { email, activityId, attributes, contactData } = await request.json();3132 if (!email || !activityId) {33 return NextResponse.json(34 { error: 'email and activityId are required' },35 { status: 400 }36 );37 }3839 // Ensure the contact exists in Ortto before firing the activity40 if (contactData) {41 await upsertContact({ email, ...contactData });42 }4344 const result = await trackActivity(email, activityId, attributes);4546 return NextResponse.json({47 success: true,48 activitiesCreated: result.count,49 });50 } catch (error) {51 const message = error instanceof Error ? error.message : 'Failed to track activity';52 return NextResponse.json({ error: message }, { status: 500 });53 }54}Pro tip: Always upsert the contact before firing an activity for a new user. If you fire an activity for an email that does not exist in Ortto, the activity may be lost (depending on your Ortto plan settings). The safest pattern is to call person/merge first, then activity/create — the merge is idempotent so calling it for existing users has no side effects.
Expected result: Firing an activity from the test page creates an activity record visible in Ortto's contact timeline and triggers any journeys configured to listen for that activity ID.
Set Up Ortto Webhooks After Deployment
Set Up Ortto Webhooks After Deployment
Ortto can send webhook notifications to your Bolt.new app when specific events occur in Ortto's system — for example, when a contact is added to a journey, when an email is opened, or when a custom activity is completed. These outgoing webhooks from Ortto's servers let your app react to marketing automation events in real time. During development in Bolt's WebContainer, incoming webhooks cannot reach the browser-based runtime. Ortto's servers need to POST to a publicly accessible URL, and the WebContainer has no public URL — it runs inside a browser tab. This means webhook handlers must be built and tested after deploying to Netlify or Bolt Cloud. Once deployed, configure Ortto webhooks in Settings → Integrations → Webhooks → Add Webhook. Enter your deployed URL (e.g., https://your-app.netlify.app/api/webhooks/ortto) and select the events you want to receive. Ortto sends webhooks as JSON POST requests with event type and payload data in the body. Create a webhook handler API route that parses the incoming event and processes it — for example, updating a Supabase record when a contact completes a journey, logging email open events, or triggering a Slack notification when a high-value activity completes. Ortto webhook requests may include a signature header for verification (check Ortto's current documentation for the verification method, as it varies by Ortto version). Always return a 200 response quickly — process the webhook event asynchronously if the processing takes more than a few seconds. A common pattern is to save the raw webhook payload to a Supabase table immediately, then process it in a separate background operation. For production deployments, set ORTTO_API_KEY as a server-side environment variable in Netlify (Site settings → Environment variables) without the NEXT_PUBLIC_ prefix. Update this variable if you ever rotate your Ortto API key.
Create an Ortto webhook handler at app/api/webhooks/ortto/route.ts. The route should accept POST requests, log the event type from the request body, and handle these event types: 'contact.activity_created' (save to Supabase ortto_events table), 'contact.email_opened' (update last_email_opened_at in Supabase users table), and 'contact.unsubscribed' (update marketing_opt_out boolean in Supabase). Return 200 immediately after receiving any event. Also create a Supabase migration for the ortto_events table: id, event_type, contact_email, payload (jsonb), received_at. Include a netlify.toml for Netlify deployment.
Paste this in Bolt.new chat
1// app/api/webhooks/ortto/route.ts2import { NextResponse } from 'next/server';3import { createClient } from '@supabase/supabase-js';45interface OrttoWebhookPayload {6 event: string;7 contact?: { email?: string; id?: string };8 activity?: { type: string; attributes?: Record<string, unknown> };9 [key: string]: unknown;10}1112export async function POST(request: Request) {13 let payload: OrttoWebhookPayload;1415 try {16 payload = await request.json();17 } catch {18 return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 });19 }2021 const supabase = createClient(22 process.env.NEXT_PUBLIC_SUPABASE_URL!,23 process.env.SUPABASE_SERVICE_ROLE_KEY! // Service role bypasses RLS for event logging24 );2526 // Log all events to Supabase for auditing27 await supabase.from('ortto_events').insert({28 event_type: payload.event,29 contact_email: payload.contact?.email || null,30 payload,31 received_at: new Date().toISOString(),32 }).then(({ error }) => {33 if (error) console.error('Failed to log Ortto event:', error);34 });3536 // Handle specific event types37 switch (payload.event) {38 case 'contact.unsubscribed':39 if (payload.contact?.email) {40 await supabase41 .from('users')42 .update({ marketing_opt_out: true })43 .eq('email', payload.contact.email);44 }45 break;4647 case 'contact.email_opened':48 if (payload.contact?.email) {49 await supabase50 .from('users')51 .update({ last_email_opened_at: new Date().toISOString() })52 .eq('email', payload.contact.email);53 }54 break;5556 default:57 console.log(`Ortto webhook received: ${payload.event}`);58 }5960 // Always return 200 quickly61 return NextResponse.json({ received: true });62}Pro tip: After deploying and registering the webhook URL in Ortto, use Ortto's webhook test feature (Settings → Integrations → Webhooks → your webhook → Test) to send a sample event to your endpoint. Check your Supabase ortto_events table to confirm the event was received and logged correctly.
Expected result: After deploying to Netlify and registering the webhook URL in Ortto, test events from Ortto's webhook settings panel are received by the handler, logged to Supabase, and the appropriate database updates occur.
Common use cases
User Onboarding Journey Trigger
When a new user completes sign-up in a Bolt.new app, create them as a contact in Ortto and fire a 'User Registered' custom activity that triggers an onboarding email sequence. As users complete onboarding steps (set up profile, created first project, invited a team member), fire additional activities that advance them through the journey or trigger specific follow-up emails.
Create an Ortto integration for user onboarding in Next.js. Build an API route at app/api/ortto/register/route.ts that accepts a POST request with user email, first name, last name, and plan type. Call Ortto's API to upsert the contact (POST https://api.ap3api.com/v1/person/merge) with the user's fields, then fire a 'user_registered' custom activity. Use process.env.ORTTO_API_KEY in the Authorization header. Call this route from the sign-up completion handler after Supabase creates the user. Return success/failure status.
Copy this prompt to try it in Bolt.new
Behavioral Activity Tracking Dashboard
Build an internal dashboard that tracks which custom activities have been fired for a user, showing their journey progress through your onboarding funnel. Combines Ortto contact data with Supabase-stored activity logs to show conversion rates at each funnel stage.
Build a user activity tracking system in Next.js. Create an API route app/api/ortto/activity/route.ts that accepts POST requests with person_email, activity_name, and attributes object. Send the custom activity to Ortto's API at POST https://api.ap3api.com/v1/activity/create. Also save the activity to a Supabase 'user_activities' table for local analytics. Build a funnel analytics page at /admin/funnel showing: total users at each stage (registered, completed profile, used core feature, upgraded), conversion rates between stages as percentages, and a list of users stuck at each stage with days since registration.
Copy this prompt to try it in Bolt.new
Contact Import and Segmentation
Build a Bolt.new admin tool for importing user data from CSV and creating Ortto contacts in bulk, with custom fields for plan type, usage metrics, and NPS scores. Uses Ortto's merge API to upsert contacts without creating duplicates.
Create a contact import tool for Ortto in Next.js. Build a page at /admin/import with a CSV upload input (accept .csv files). Parse the CSV using papaparse npm package — expect columns: email, first_name, last_name, plan, usage_count. Build an API route app/api/ortto/import/route.ts that accepts an array of contacts and sends them to Ortto in batches of 50 using the person/merge endpoint. Show progress: total contacts, imported count, failed count, and error details. After import, show a summary of newly created vs updated contacts.
Copy this prompt to try it in Bolt.new
Troubleshooting
Ortto API returns 401 Unauthorized on all requests
Cause: The API key is incorrect, expired, or the Authorization header format is wrong. Ortto expects 'Bearer {apiKey}' not 'Token {apiKey}' or just the raw key.
Solution: Verify the ORTTO_API_KEY in your .env matches the key shown in Ortto → Settings → API Keys. Ensure the Authorization header format is exactly 'Bearer ' followed by the key. If the key was recently regenerated in Ortto, update it in your environment variables.
1// Correct Authorization header format for Ortto2const headers = {3 Authorization: `Bearer ${process.env.ORTTO_API_KEY}`,4 'Content-Type': 'application/json',5};6// NOT: 'Token apiKey' or 'Apikey apiKey'person/merge creates a new contact instead of updating the existing one
Cause: The merge_by field is not set to deduplicate by email, causing Ortto to create a new contact even when one with that email already exists.
Solution: Include 'merge_by': ['str::email'] in the request body to tell Ortto to use email as the deduplication key. Without merge_by, Ortto may use its default deduplication strategy which may not match on email.
1const body = {2 fields: { 'str::email': email, 'str::first': firstName },3 merge_by: ['str::email'], // Required for email-based deduplication4 find_strategy: 0, // 0 = upsert (create or update)5};Custom activity fires successfully (200 response) but no journey is triggered in Ortto
Cause: The activity_id in the API call does not exactly match the activity ID configured in Ortto, or the journey is not set to trigger on this activity, or the contact does not yet exist in Ortto.
Solution: Verify the activity_id exactly matches what is shown in Ortto → Data → Activities (it is case-sensitive). Ensure the contact exists in Ortto before firing the activity. Check the journey trigger configuration in Ortto — the journey must be Active and set to start when the specific activity occurs.
Ortto webhooks are never received despite being registered
Cause: The webhook URL was registered pointing to the Bolt WebContainer preview URL, which is not publicly accessible. Ortto cannot send POST requests to a browser-based runtime.
Solution: Deploy to Netlify or Bolt Cloud to get a publicly accessible URL, then register the deployed URL in Ortto's webhook settings. The WebContainer development environment cannot receive incoming HTTP connections from Ortto's servers.
Best practices
- Always upsert the contact (person/merge) before firing custom activities — Ortto activities for unknown email addresses may be silently dropped depending on your account configuration
- Use snake_case activity IDs that describe what happened in the past tense: 'onboarding_completed', 'subscription_upgraded', 'feature_first_used' — these are clearer in Ortto's journey builder than present-tense names
- Store the Ortto person_id returned from person/merge in your Supabase users table — this enables fast lookups without having to re-query Ortto by email on every activity
- Keep ORTTO_API_KEY as a server-side-only environment variable with no NEXT_PUBLIC_ prefix — the API key grants full access to your contact database and must never appear in client-side code
- Deploy to Netlify or Bolt Cloud before testing Ortto webhook integrations — incoming webhook events from Ortto cannot reach the Bolt WebContainer development environment
- Log all incoming Ortto webhooks to a Supabase table before processing — this gives you an audit trail and lets you replay missed events if processing fails
- Test your custom activity definitions in Ortto's sandbox before building journeys around them — verify the activity appears in a contact's timeline with the correct attributes before wiring up automation
Alternatives
HubSpot covers broader CRM and sales pipeline management alongside marketing automation, with more robust free tier features but a more complex API, making it better when you need sales CRM alongside marketing journeys.
Mailchimp focuses primarily on email marketing with simpler automation compared to Ortto's visual journey canvas, but has a more accessible free tier and a well-documented API that is easier to get started with.
Drip is designed specifically for e-commerce marketing automation with deep Shopify integration and revenue-focused workflows, while Ortto is more versatile for SaaS and B2B customer journey mapping.
ConvertKit focuses on email marketing for creators and content businesses with a simpler automation model, while Ortto is better suited for SaaS products with complex multi-channel customer journeys.
Frequently asked questions
What is the difference between Autopilot and Ortto?
Ortto is the current brand name for the company formerly known as Autopilot. The product rebranded to Ortto in 2022. The platform, API, and core functionality are the same product under the new name. The API base URL changed from api.autopilothq.com to api.ap3api.com. If you find documentation, tutorials, or forum posts referencing Autopilot's API, the concepts are valid but some endpoint paths and field names may have changed. Always use the current Ortto documentation at ortto.com/product/api.
How do Custom Activities work in Ortto?
Custom Activities are events you define in Ortto that represent meaningful actions users take in your application. When your Bolt.new app calls the activity/create API endpoint, Ortto records the activity in the contact's timeline and triggers any journeys that are configured to start when that activity occurs. Activities can have attributes (data attached to each instance) that journeys can use for personalization or conditional branching. Define activities in Ortto → Data → Activities before referencing them in API calls.
Can I test Ortto webhooks in Bolt.new's development preview?
No. Ortto sends webhook events as HTTP POST requests to your registered endpoint URL. Bolt's WebContainer runs inside a browser tab without a public URL, so Ortto's servers cannot reach it. Deploy to Netlify or Bolt Cloud to get a public URL, register it in Ortto's webhook settings, then use Ortto's built-in webhook test feature to verify your handler receives and processes events correctly.
How is Ortto different from HubSpot for Bolt.new integrations?
Ortto focuses specifically on visual customer journey mapping and marketing automation — its core value is the journey canvas where you drag and drop conditions and actions to build automated sequences. HubSpot covers a broader CRM with sales pipelines, contact management, and marketing features. HubSpot has a more complex API and a larger free tier; Ortto's API is simpler for basic contact and activity tracking. Choose Ortto when customer journey visualization is the priority; choose HubSpot when you need full CRM functionality with a sales pipeline.
What happens if I fire an activity for an email that doesn't exist in Ortto?
Behavior varies by Ortto account settings. In some configurations, Ortto will create a new contact from the email address when an activity is fired for an unknown email. In others, the activity may be dropped. To avoid uncertainty, always call person/merge before activity/create for new users — merge with the user's email creates the contact if it does not exist, and the immediately following activity/create will find the contact reliably.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation