AcquisitionPRO® / CRM Integration
AcquisitionPRO® is the WALDO-bundled AcquisitionPRO® or your CRM deployment. If your CRM is AcquisitionPRO® or your CRM directly (not under the AcquisitionPRO® brand), this all still applies — same API, same configuration, same behavior.
The AcquisitionPRO® / CRM integration pushes a note + structured data to specific contacts in your CRM whenever a WALDO analysis completes. There are two ways to use it, and you can use both at once:
- Standing rules (→ AcquisitionPRO® / CRM section) — set up once, fires forever. "Whenever any analysis on these markets finishes, post a note on this contact." Great for keeping a personal CRM trail or auto-updating a retainer client's record.text
/settings/integrations - Per-export ad-hoc (Phase 6 Results dashboard → "Send to webhook / email / CRM" tile) — pick contacts at send time. "Send this specific analysis to broker X, client Y, and our internal ops contact." Great for consultants juggling multiple clients where the recipients change deal by deal.
TL;DR for CRE professionals: Stop manually telling clients "your analysis is ready" and stop copy-pasting numbers into CRM notes. Either set up a standing rule (auto-fires forever) OR pick contacts on the Phase 6 export tile (ad-hoc). Both push a clean note to the contact's timeline with the market name, completion timestamp, and a quick summary block — and update a structured custom field automations can read.
What you get from this
- Automatic note timeline. Every analysis that matches a mapping creates a note on the contact in AcquisitionPRO® or your CRM. Your team's history with that contact stays current without anyone copy-pasting.
- Structured data field. A custom field on the contact gets a JSON snapshot of the latest analysis (market, completion date, top metrics). AcquisitionPRO® or your CRM automations can trigger off this — send an SMS, kick off a workflow, branch on the value.text
waldo_latest_analysis - Per-mapping market filter. Don't push every analysis to every contact. Map "Acquisition lead — ACME Holdings" to only Cuyahoga + Hamilton county FIPS codes; only those analyses fire notes on that contact.
- Audit log. Every push attempt is logged with the contact, the analysis ID, success/failure, and any error message. You see at a glance whether AcquisitionPRO® or your CRM is keeping up.
- Pause and resume. Each mapping has its own active/paused toggle. The settings page also has a "Pause all" / "Resume all" bulk button.
Real-world workflows
| Mapping | What it does |
|---|---|
| Map your primary contact (the broker) to all markets (no filter) | Every completed analysis you run posts a note on the broker's record. Useful for solo operators who want a personal CRM trail. |
| Map a specific client contact (e.g. "Smith Family Office") to the markets you've pitched them | Only analyses on their target markets create notes on their record. Other analyses don't pollute their timeline. |
| Map an internal "Ops Inbox" contact to all markets | Your ops team sees every analysis-completed note in one place; the broker's contacts only see their relevant ones. |
| Map a competitor-tracking contact to specific MSAs | Notes accrue automatically as you run quarterly updates on the markets that competitor cares about. |
Step-by-step: setting up a standing rule
- Sign in as a Professional or Enterprise user. AcquisitionPRO® / AcquisitionPRO® or your CRM must already be provisioned for your WALDO account (admin handles this — if you're on AcquisitionPRO® it's already configured).
- Go to Settings → Integrations.
- Find the AcquisitionPRO® / CRM section (below Scheduled Exports).
- Click "Add mapping."
- Search by name or email — the autocomplete queries your AcquisitionPRO® or your CRM location for matching contacts. Pick the one you want.
- Set a label (e.g. Acquisition lead — ACME Holdings). This is for your reference in the WALDO settings page; it doesn't affect what AcquisitionPRO® or your CRM sees.
- Optionally, add a market filter. Comma-separated FIPS codes (). Leave blank to push every completed analysis.text
39035, C26900 - Click "Add mapping." Done.
- Test it by completing an analysis on one of the filtered markets (or any market if no filter). Within ~2 minutes the events-outbox subscriber will pick up the analysis-completed event and post a note + custom-field update. Watch the Recent pushes panel below the mappings list — you'll see the row with status "Succeeded" (green) or "Failed" (red).
Step-by-step: ad-hoc per-export send
For consultants serving multiple clients (where the recipients differ deal by deal), skip standing rules and use the export tile instead:
- Open any completed analysis in WALDO and go to the Results & Reporting tab (Phase 6).
- Click the Export sub-tab.
- Click the amber "Send to webhook / email / CRM" tile.
- In the modal:
- Pick format (PDF / Excel / PowerPoint).
- Optionally pick one or more registered webhook endpoints.
- Optionally enter email recipients (your account email is pre-filled).
- Search for AcquisitionPRO® / CRM contacts in the multi-select chip field. Type a name or email; matching contacts appear; click to add. Each picked contact appears as a removable chip. Add as many as the deal needs.
- Click Send. WALDO generates the export once and fans out: emails go out, webhooks fire, AND a note + custom-field update is pushed to each picked CRM contact.
The ad-hoc path uses the same
addAnalysisNotepushAnalysisSummarymapping_id = nullWhat gets written to the contact
For each mapping that fires, WALDO writes:
Note (POST
/contacts/{id}/notestext📊 <your org name> analysis completed: Cuyahoga County Market type: county (39035) Completed: Wed, 30 Apr 2026 13:00:00 GMT Top LQ sector: NAICS 622 @ LQ 1.34 Net shift-share: +1,245 jobs Supply gap: -125 units Analysis ID: 5fa9c0a2-...
The note title uses your org's white-label
companyNameCustom field (PUT
/contacts/{id}customFields: [{ key: "waldo_latest_analysis", field_value: "..." }]json{ "source": "<your org name>", "analysis_id": "5fa9c0a2-...", "market_name": "Cuyahoga County", "market_type": "county", "market_code": "39035", "completed_at": "2026-04-30T13:00:00Z", "top_lq": { "naics": "622", "lq": 1.34 }, "net_shift_share": 1245, "supply_gap": -125 }
The custom field is best-effort — if your AcquisitionPRO® or your CRM location doesn't have a
waldo_latest_analysisHow to add the textwaldo_latest_analysis
custom field in AcquisitionPRO® (AcquisitionPRO® or your CRM)
waldo_latest_analysisYou'll do this once per AcquisitionPRO® or your CRM location — about two minutes.
- Sign in to AcquisitionPRO® / AcquisitionPRO® or your CRM as a user with permission to edit settings (sub-account admin or agency admin).
- In the left sidebar, click the gear icon (Settings) at the bottom.
- In the settings menu, click Custom Fields.
- Make sure the Contact tab is selected at the top (custom fields can be scoped to Contact, Opportunity, etc. — we want Contact).
- Click + Add Field (top right).
- Pick the field type Multi Line / Large Text. (Single Line works too but caps at ~255 chars; Large Text is safer because the JSON payload can run 200–600 chars on busy analyses.)
- Fill in:
- Name (label): text
WALDO Latest Analysis - Field Key: (this exact value — lowercase, underscores, no spaces — is what WALDO writes to)text
waldo_latest_analysis - Group / Folder: optionally create a "WALDO" group to keep it organized
- Leave Required, Show in Forms, and Placeholder at their defaults
- Name (label):
- Click Save.
- (Optional but useful) Open any contact in AcquisitionPRO® or your CRM and confirm the new field appears in the contact's profile sidebar. It will be empty until WALDO pushes its first update.
That's it. The next time an analysis completes that matches one of your mappings, both the note AND the structured custom field will populate. You can wire AcquisitionPRO® or your CRM automations off
Contact.WALDO Latest AnalysisField UpdatedVerifying it worked: after a push, open the contact in AcquisitionPRO® or your CRM → scroll the right sidebar to the WALDO group → you should see a JSON object pasted into the field. If the field stays blank but a new note still posted on the contact's timeline, the most likely cause is a misspelled field key (it must be exactly
waldo_latest_analysisPause, resume, delete
- Pause one mapping — that mapping stops firing; others keep working.
- Pause all — bulk toggle every mapping inactive in one click. Useful for going on vacation or pausing during data investigations.
- Delete — removes the mapping. Past push history is preserved on .text
highlevel_push_log
Tier requirements
AcquisitionPRO® or your CRM push is included on Professional and Enterprise plans. Push activity does not consume API quota — it's an internal subscriber, not a user-facing API call.
The integration uses the platform-level AcquisitionPRO® or your CRM API key set in WALDO's environment, not a per-user OAuth token. All your contact pushes go to the same AcquisitionPRO® or your CRM location WALDO is configured against. If you operate multiple AcquisitionPRO® or your CRM locations and need per-user routing, contact support.
For your integrator (or AI agent)
Everything below is the technical reference. Paste a link to this page into Claude / ChatGPT / Cursor and ask the AI to wire up the integration — it has all the information it needs.
Architecture
- WALDO has a single +text
HIGHLEVEL_API_KEYset at the deployment level (env vars). All pushes go through this single key against this single location. There is no per-user OAuth.textHIGHLEVEL_LOCATION_ID - A user adds rows to via the settings UI. Each mapping pairs a AcquisitionPRO® or your CRMtext
highlevel_contact_mappingswith an optional market filter (textcontact_id).textmarket_codes TEXT[] - Vercel Cron runs every 2 minutes. It readstext
/api/internal/jobs/process-highlevel-eventsrows wheretextevents_outboxANDtextevent_name='analysis.completed', finds matching mappings for each event's user, and pushes a note + custom-field update via the AcquisitionPRO® or your CRM API.texthl_processed_at IS NULL - Each push attempt is recorded on (success or failure). The event row'stext
highlevel_push_logis marked regardless of push outcome — failed pushes don't infinitely retry. Users can re-run a failed push manually by deleting + recreating the mapping (or by triggering a new analysis on the same market).texthl_processed_at
Schema
highlevel_contact_mappings- text
id, user_id, hl_contact_id, hl_contact_email, hl_contact_label, market_codes TEXT[], active, created_at, updated_at
highlevel_push_log- text
id, user_id, mapping_id, analysis_id, event_id, hl_contact_id, hl_contact_email, status, error_message, pushed_at
events_outbox- Added column — independent dimension fromtext
hl_processed_at TIMESTAMP WITH TIME ZONE(which the webhook fanout uses) so the AcquisitionPRO® or your CRM subscriber and webhook processor don't race.textprocessed_at
Library API
lib/integrations/highlevel-api.ts- — autocomplete by name or email.text
searchContact(query: string): Promise<AcquisitionPRO® or your CRMSearchHit[]> - — POSTtext
addAnalysisNote(contactId, summary, brandingName): Promise<{ success, error? }>.text/contacts/{id}/notes - — PUTtext
pushAnalysisSummary(contactId, summary, brandingName): Promise<{ success, error? }>updating thetext/contacts/{id}custom field.textwaldo_latest_analysis
AnalysisSummaryForHLts{ analysisId: string marketName: string marketType: string marketCode: string | null completedAt: string | null topLocationQuotient?: { naics: string; lq: number } | null netShiftShare?: number | null supplyGap?: number | null }
Internal management API (Supabase JWT auth)
| Method | Path | Description |
|---|---|---|
| GET | text | List caller's mappings |
| POST | text | Create mapping |
| GET | text | Fetch one |
| PATCH | text | Update label, market_codes, active |
| DELETE | text | Delete |
| GET | text | Autocomplete proxy (HL API key never exposed to browser) |
| GET | text | Recent pushes |
Cron details
*/2 * * * *Authorization: Bearer ${CRON_SECRET}{ processed, pushed, failed }{ skipped: 'AcquisitionPRO® or your CRM not configured' }Failure modes + retry semantics
- unset — the cron returns early with no work done. No mappings fire until env is set.text
HIGHLEVEL_API_KEY - HL API returns 4xx/5xx — push is logged as with the response body. Event is still markedtext
failed— no infinite retry. User can recreate the mapping or trigger a new analysis to retry.texthl_processed_at - Custom field write fails (e.g., field not provisioned) — note write succeeds, status is , error_message includestext
succeeded. Note is the load-bearing path.textfield: ... - Mapping market filter excludes the analysis — no push, no log row. Silent.
Privacy + data flow
The only data sent to AcquisitionPRO® or your CRM:
- The contact ID (already known to AcquisitionPRO® or your CRM).
- A note body containing market name, FIPS code, completion timestamp, optional top-line metrics, and the WALDO analysis ID.
- A JSON-stringified summary written to the custom field.text
waldo_latest_analysis
No PII from your client list crosses to AcquisitionPRO® or your CRM. Mappings are user-scoped; one user's mappings cannot push to another user's contacts.