← Agent Frameworks 🕐 16 min read
Agent Frameworks

Salesforce Migration Alternatives and API Proxy Patterns (2026)

Two distinct problems share this document. The first is operational: should a company migrate off Salesforce, and if so, to what?

Two distinct problems share this document. The first is operational: should a company migrate off Salesforce, and if so, to what? The second is architectural: if an engineering team is building a custom CRM to replace Salesforce, how do they validate API compatibility before flipping traffic? Both questions have concrete, practitioner-grade answers in 2026. Neither is a solved problem — both require real engineering work and budget honesty.


Part 1 — Salesforce Migration Alternatives

1.1 Why Companies Migrate Off Salesforce

Salesforce dominates enterprise CRM with ~23% global market share (IDC, 2025), but churn is rising in the 10–250 seat band. The proximate causes cluster around three vectors:

Cost escalation. Sales Cloud Enterprise lists at $165/user/month as of 2026, but true total cost of ownership is materially higher once you add:

Cost Component Typical Range
Sales Cloud Enterprise license $165/user/month
Marketing Cloud Add-on $400–$3,500/month flat
CPQ (Salesforce Revenue Cloud) $75/user/month add-on
AppExchange ISV apps (avg 3–5) $20–$80/user/month
Admin salary (1 FTE per 50 seats) ~$110,000–$145,000/year
Salesforce storage overages $5–$10/GB/month above 10GB
AI/Einstein consumption fees variable, $75–$150/user/month
Realistic loaded cost $300–$500/user/month

At 50 users, that is $180,000–$300,000/year just for the platform. At 200 users, a Fortune 500 subsidiary can be spending $720,000–$1.2M/year before touching services or integrations.

Complexity accumulation. Salesforce orgs age poorly. Every process builder flow, legacy workflow rule, custom object relationship, and Apex trigger added over 5–10 years compounds maintenance burden. Salesforce’s own migration from Process Builder to Flow has created a large installed base of orgs running deprecated automation that nobody wants to touch.

Vendor lock-in. Proprietary SOQL, Apex (proprietary OOP language), VisualForce and Aura (legacy front-end frameworks), Lightning Web Components, and the Metadata API all create moat. Data portability is real — Salesforce’s Bulk API 2.0 exports everything — but the surrounding logic, automation, and integrations do not travel.


1.2 Realistic Cheaper Alternatives

Tier 1 — Commercial SaaS (Significant Savings, Low Migration Risk)

HubSpot CRM

  • Free tier: unlimited contacts and deals, core pipeline views, email integration. Functional for teams under 10 not needing sequences or reporting.
  • Sales Hub Starter: $20/user/month (billed annually). Adds email sequences, meeting scheduling, simple pipelines.
  • Sales Hub Professional: $100/user/month. Adds custom reporting, forecasting, playbooks, deal automation. This is the Salesforce Professional analog.
  • Sales Hub Enterprise: $150/user/month. Adds custom objects, predictive forecasting, call transcription.
  • Migration advantage: HubSpot maintains a native Salesforce Data Sync integration that can run bidirectionally during transition. Standard objects (Contacts, Companies, Deals, Activities) map cleanly. Custom objects require HubSpot Enterprise.

Pipedrive

  • Essential: $14/user/month. Core pipeline, activities, email sync.
  • Advanced: $29/user/month. Automation, email sequences, scheduler.
  • Professional: $59/user/month. Revenue forecasting, custom reporting, multiple pipelines.
  • Power: $69/user/month. Collaboration tools, project management layer.
  • Enterprise: $99/user/month. Full audit logs, SSO, enhanced support.
  • Best fit: Pure sales teams with a linear deal process. Weak on marketing automation and customer success workflows. Excels at pipeline visualization and activity tracking.

Zoho CRM

  • Standard: $14/user/month. Basic CRM, 10 custom fields, email integration.
  • Professional: $23/user/month. SalesSignals, web forms, scoring rules, inventory.
  • Enterprise: $40/user/month. Zia AI, multi-user portals, advanced analytics.
  • Ultimate: $52/user/month. Full feature set, dedicated BI analytics.
  • Migration advantage: Zoho offers a native Salesforce Migration Wizard (free, self-serve) that migrates Accounts, Contacts, Leads, Opportunities, Cases, and custom modules. Field mapping is automated with manual review required for custom picklists and formulas. Zoho’s ecosystem (Zoho Books, Campaigns, Desk, Projects) rivals Salesforce’s breadth at a fraction of the cost.

Tier 2 — Open Source / Self-Hosted (Maximum Savings, Real Ops Overhead)

Twenty CRM (twenty.dev / twentyhq/twenty on GitHub)

Twenty is the highest-momentum open-source CRM in 2026. As of June 2026:

  • 44,000+ GitHub stars, 6,000+ forks, 300+ contributors
  • Stack: TypeScript monorepo, NestJS back-end, React front-end, PostgreSQL, Redis
  • APIs: REST + GraphQL (auto-generated from metadata definitions)
  • Licensing: AGPL-3.0 (self-hosted free forever, no user caps, no feature gates)
  • Cloud managed (app.twenty.com): $9/user/month
  • Deployment: Docker Compose (single-node), Kubernetes Helm chart (production)

Twenty’s architecture is fundamentally different from legacy CRMs: objects (People, Companies, Deals) are defined as metadata in the database, not hard-coded. This means custom objects are a first-class primitive, not an afterthought. The GraphQL API surface auto-generates to include any custom object instantly.

Capabilities in 2026:

  • Custom objects with arbitrary field types (text, number, relation, select, date, file, link)
  • Kanban and table views with filter/sort/group
  • Email and calendar sync (Gmail, Outlook via OAuth)
  • Timeline view of all activities per record
  • Workflow automation (trigger → action chains, beta as of early 2026)
  • REST and GraphQL APIs fully documented at /api
  • Webhook support for outbound events

Gaps vs. Salesforce (as of mid-2026):

  • No native CPQ / Revenue Cloud equivalent
  • No AppExchange ecosystem (third-party integrations are DIY or via Zapier/n8n)
  • Workflow automation is early-stage (Salesforce Flow equivalent not yet complete)
  • No built-in telephony, predictive scoring, or Einstein-class AI
  • Territory management absent
  • Reporting is functional but not Salesforce Analytics depth

SuiteCRM

  • Open-source fork of SugarCRM Community Edition (SalesAgility, UK)
  • ~5M claimed users across enterprise, government, NGOs
  • Stack: PHP 8, MySQL/MariaDB/PostgreSQL, Smarty templates
  • Closest feature parity to Salesforce of any open-source option
  • Modules: Accounts, Contacts, Leads, Opportunities, Cases, Campaigns, Contracts, Products, Quotes, Reports, Workflow (AOW), Email Marketing
  • Critical limitation: UI and codebase are dated (PHP MVC, no modern front-end). New contributor velocity is slower than Twenty. Not API-first.
  • Best for: organizations with Salesforce-parity requirements, in-house PHP/LAMP expertise, no need for a modern developer experience.

Erxes

  • Open-source, TypeScript/Node, React, MongoDB
  • Focuses on marketing + sales + support (XOS architecture — “Experience Operating System”)
  • Plugin-based architecture; each module (inbox, CRM, marketing) is a separate plugin
  • Better for companies that need a unified customer engagement platform, not just a sales pipeline
  • Self-hosted free, cloud plans from $149/month flat

1.3 Decision Framework: Who Should Migrate vs. Stay

┌──────────────────────────────────────────────────────────────────┐
│                    MIGRATION DECISION MATRIX                     │
├─────────────────────┬────────────────────────────────────────────┤
│ SIGNAL              │ RECOMMENDATION                             │
├─────────────────────┼────────────────────────────────────────────┤
│ < 25 seats          │ Migrate. Salesforce ROI almost never       │
│                     │ justifies cost below this threshold.       │
│                     │ Target: HubSpot Starter or Twenty CRM.     │
├─────────────────────┼────────────────────────────────────────────┤
│ 25–100 seats,       │ Migrate with planning. 6–12 month          │
│ few custom objects  │ timeline realistic. HubSpot Pro or         │
│                     │ Zoho Enterprise are lowest-risk landings.  │
├─────────────────────┼────────────────────────────────────────────┤
│ 25–100 seats,       │ Evaluate carefully. Custom object          │
│ heavy customization │ migration is the primary risk. Requires    │
│                     │ dedicated data engineering work.           │
├─────────────────────┼────────────────────────────────────────────┤
│ >100 seats,         │ Stay or rebuild internally. Integration    │
│ deep integrations   │ dependency graph is the real lock-in,      │
│ (ERP, CPQ, etc.)    │ not the license. ROI on migration is       │
│                     │ negative unless contract is expiring.      │
├─────────────────────┼────────────────────────────────────────────┤
│ Engineering-led     │ Consider Twenty CRM (self-hosted) or       │
│ company, Postgres   │ build on Twenty's API layer. Total cost    │
│ fluent ops team     │ including hosting: $5–15K/year for 50      │
│                     │ users vs. $100K+ for Salesforce.           │
├─────────────────────┼────────────────────────────────────────────┤
│ Revenue Cloud /     │ Do not migrate. No credible alternative    │
│ CPQ in production   │ replicates Salesforce CPQ depth. Cost of   │
│                     │ rebuilding quoting logic exceeds savings.  │
├─────────────────────┼────────────────────────────────────────────┤
│ Salesforce-native   │ Do not migrate. Apex, custom LWC, and      │
│ code in production  │ Metadata API deployments are not portable. │
│ (Apex / LWC)        │ Rewrite cost is 2–3x migration savings.    │
└─────────────────────┴────────────────────────────────────────────┘

1.4 Migration Complexity and Real Costs

What Moves Cleanly

Standard object data (Accounts, Contacts, Leads, Opportunities, Activities, Tasks, Events) exports via Salesforce’s native Weekly Data Export or Bulk API 2.0 as CSV or JSON. Field types are well-documented. Most target CRMs provide import wizards for these.

What Requires Engineering Work

Migration Component Effort Notes
Standard object data Low CSV export → import wizard
Custom object data Medium Must map fields manually; picklist values need reconciliation
Custom picklist dependencies Medium Controlling field → dependent field logic must be rebuilt
Workflow Rules (legacy) High No export format; must be read from Salesforce UI and rebuilt
Process Builder flows High Deprecated by Salesforce; must be rewritten in target platform
Salesforce Flow Very High Visual flows export as XML metadata but are proprietary; no standard
Apex triggers Very High Proprietary language; logic must be rewritten in target language
Email templates Medium HTML-based; portable with reformatting
Reports and dashboards High SOQL-backed reports do not port; must be rebuilt
AppExchange ISV integrations Very High Each ISV must be replaced with a native integration or rebuilt
ERP / billing integrations Very High Often the actual migration-killer; re-architecture required
User permissions / profiles / roles Medium Role hierarchy logic must be rebuilt; RBAC models differ
Document generation High Salesforce Doc Gen is proprietary; tools like Conga require alternatives

Realistic Timeline

Month 1–2:   Audit and inventory
             - Map all objects, fields, automations, integrations
             - Score each component (moves clean / needs rebuild / blocks migration)
             - Select target platform
             - Sign contract with clear data portability provisions

Month 2–3:   Data extraction and cleansing
             - Bulk API 2.0 export of all objects
             - Deduplication (expect 10–30% duplicate rate in aging orgs)
             - Field normalization (phone formats, country codes, etc.)

Month 3–6:   Target platform build
             - Custom object definitions
             - Workflow/automation rebuild
             - Integration reconnection (one by one)
             - Permission model configuration

Month 6–9:   Parallel run
             - Both systems live; new records enter target CRM
             - Salesforce used as read-only reference
             - Issue triage and fix cycle

Month 9–12:  Cutover and decommission
             - Final delta sync from Salesforce
             - Training and change management
             - Salesforce contract non-renewal

Month 12–18: Stabilization
             - Long-tail integrations reconnected
             - Reporting rebuilt to equivalent coverage
             - Salesforce license termination

Hidden Cost Inventory

The decision to migrate often looks like a no-brainer on a spreadsheet until someone does a proper audit. Budget for:

  • Data cleansing: $5,000–$60,000 depending on volume and quality. A 50,000-record migration with 15,000 duplicates and no naming standards can take 2–3x longer than a clean export.
  • Integration re-architecture: $10,000–$100,000+. Every Salesforce integration (Marketo, Netsuite, Zendesk, Docusign, Slack) must be individually re-plumbed. Middleware costs (Zapier, Make, Tray.io, or custom Node/Python services) add up.
  • Workflow rebuilds: $20,000–$80,000 for complex orgs. Rule of thumb: 1 week of developer time per 10 non-trivial automation rules.
  • Retraining: $500–$5,000 per user (Salesforce’s own implementation cost guides cite this range). Salesforce-trained users have muscle memory that does not transfer. Budget for role-specific training, not generic onboarding.
  • Lost productivity during transition: Typically 30–60 days of below-normal output from the sales team during cutover. Hard to quantify, real to measure.
  • Consultant fees: If using a migration partner, add $15,000–$80,000 for a typical SMB migration.

Rule of thumb: Total migration cost (one-time) equals 6–18 months of the Salesforce license being replaced. The break-even point for a 50-seat org moving from Salesforce Enterprise to HubSpot Pro is typically month 18–24.


Part 2 — Salesforce API Proxy for Custom CRM

2.1 The Problem Statement

An engineering team building a custom CRM to replace Salesforce faces a specific validation challenge: existing integrations — ETL pipelines, marketing automation tools, ERP connectors, custom scripts — all speak Salesforce’s REST API. Before decommissioning Salesforce, the team needs to prove their custom CRM responds to Salesforce API calls correctly.

The cleanest approach is a record-then-validate loop:

  1. Place a transparent proxy in front of Salesforce in production
  2. Record all real API traffic (request/response pairs)
  3. Point that recorded traffic at the custom CRM
  4. Compare responses

This produces a ground-truth compatibility test corpus derived from actual production behavior, not synthetic tests.


2.2 Salesforce REST API Surface Map

A proxy that claims 100% compatibility must cover the entire API surface. Salesforce exposes seven distinct API families:

REST API (Primary — must implement)

Endpoint Pattern Purpose
GET /services/data/ API version list
GET /services/data/v{n}/ Resources available at version
GET /services/data/v{n}/limits Governor limit status
GET /services/data/v{n}/sobjects/ Object catalog
GET /services/data/v{n}/sobjects/{Object}/ Object metadata
GET /services/data/v{n}/sobjects/{Object}/describe Full field/relationship schema
POST /services/data/v{n}/sobjects/{Object}/ Create record
GET /services/data/v{n}/sobjects/{Object}/{id} Read record
PATCH /services/data/v{n}/sobjects/{Object}/{id} Update record
DELETE /services/data/v{n}/sobjects/{Object}/{id} Delete record
GET /services/data/v{n}/sobjects/{Object}/updated Changed records by time range
GET /services/data/v{n}/sobjects/{Object}/deleted Deleted records by time range
GET /services/data/v{n}/query/?q={SOQL} SOQL query
GET /services/data/v{n}/query/{queryLocator} SOQL pagination (cursor)
GET /services/data/v{n}/queryAll/?q={SOQL} SOQL including deleted records
GET /services/data/v{n}/search/?q={SOSL} SOSL full-text search
PATCH /services/data/v{n}/sobjects/{Object}/{extIdField}/{extId} Upsert by external ID
GET /services/data/v{n}/composite/sobjects Composite collections
POST /services/data/v{n}/composite/sobjects Bulk create/update (up to 200)
POST /services/data/v{n}/composite/ Composite request (up to 25 subrequests)
POST /services/data/v{n}/composite/batch Batch request (independent subrequests)
POST /services/data/v{n}/composite/tree/{Object} Tree insertion (parent + children)

Bulk API 2.0 (Required for ETL compatibility)

Endpoint Purpose
POST /services/data/v{n}/jobs/ingest Create ingest job
PUT /services/data/v{n}/jobs/ingest/{jobId}/batches Upload CSV data
PATCH /services/data/v{n}/jobs/ingest/{jobId} Close/abort job
GET /services/data/v{n}/jobs/ingest/{jobId} Job status
GET /services/data/v{n}/jobs/ingest/{jobId}/successfulResults Success records
GET /services/data/v{n}/jobs/ingest/{jobId}/failedResults Failed records
POST /services/data/v{n}/jobs/query Create query job
GET /services/data/v{n}/jobs/query/{jobId}/results Download results CSV

Authentication (OAuth 2.0 — mandatory)

Flow Use Case
Web Server Flow (Authorization Code) User-facing integrations
JWT Bearer Token Flow Server-to-server (no user interaction)
Username-Password Flow (deprecated) Legacy scripts; still in production everywhere
Device Flow IoT / CLI tools
Refresh Token Flow Token lifecycle management

The token endpoint is https://{instance}/services/oauth2/token. The userinfo endpoint is https://{instance}/services/oauth2/userinfo. A proxy must implement all of these.

Streaming / Events (Required for change-data-capture consumers)

Salesforce’s event system has two generations:

  • Streaming API (legacy): CometD/Bayeux protocol over long-polling HTTP. Endpoint: https://{instance}/cometd/{version}/. Topics: /topic/{PushTopic}, /event/{PlatformEvent__e}, /data/{Object}ChangeEvent. Requires CometD handshake → subscribe → poll cycle.
  • Pub/Sub API (current): gRPC + HTTP/2, Apache Avro binary serialization, pull-based flow control. Schema registry at /api/platform/v1/schema/{schemaId}. Recommended for new integrations.

A custom CRM targeting CDC consumers must implement either CometD long-poll server (Streaming API compatibility) or a gRPC endpoint conforming to the Pub/Sub API proto schema.

Metadata API (Required if any Salesforce tooling — Workbench, VS Code extension — is used)

SOAP-based. Exposes: retrieve, deploy, listMetadata, describeMetadata, checkRetrieveStatus, checkDeployStatus. Most migration tooling depends on this. Not required for runtime CRM compatibility but required for org-management compatibility.

Tooling API (Lower priority)

REST + SOQL-like interface for development tooling: code coverage, debug logs, test execution. Required only if Salesforce developer tooling is pointed at the custom CRM.


2.3 Proxy Architectures

Three architectural patterns cover the main use cases.

Pattern A — Transparent Pass-Through Proxy with Traffic Recording

┌──────────────────────────────────────────────────────────────────┐
│                  PATTERN A: TRANSPARENT PROXY                    │
│                                                                  │
│  Integration       Recording Proxy       Salesforce             │
│  (ETL, App,   ──►  ┌────────────────┐ ──►  ┌─────────────┐    │
│   Script)          │ nginx / Envoy  │      │  Production  │    │
│                    │                │      │    Org       │    │
│                    │ 1. Intercept   │ ◄──  └─────────────┘    │
│                    │ 2. Log req/res │                           │
│                    │ 3. Forward as- │                           │
│                    │    is to SF    │                           │
│                    └────────────────┘                           │
│                           │                                      │
│                           ▼                                      │
│                    ┌────────────────┐                           │
│                    │  Log Store     │                           │
│                    │ (Hoverfly /    │                           │
│                    │  WireMock /    │                           │
│                    │  raw JSONL)    │                           │
│                    └────────────────┘                           │
└──────────────────────────────────────────────────────────────────┘

Best for: capturing production traffic corpus without modifying integrations. Zero-risk to production.

nginx implementation sketch:

# /etc/nginx/conf.d/sf-proxy.conf

server {
    listen 443 ssl;
    server_name sf-proxy.internal;

    ssl_certificate /etc/ssl/sf-proxy.crt;
    ssl_certificate_key /etc/ssl/sf-proxy.key;

    # Log full request/response bodies for replay corpus
    lua_need_request_body on;
    body_filter_by_lua_block {
        -- Emit req+res pair to log collector
        local req_body  = ngx.req.get_body_data() or ""
        local resp_body = ngx.arg[1] or ""
        ngx.log(ngx.NOTICE,
            "SFPROXY ",
            ngx.var.request_method, " ",
            ngx.var.request_uri, " ",
            ngx.status, " ",
            ngx.encode_base64(req_body), " ",
            ngx.encode_base64(resp_body))
    }

    location / {
        proxy_pass https://your-org.my.salesforce.com;
        proxy_set_header Host your-org.my.salesforce.com;
        proxy_ssl_server_name on;

        # Credential injection: replace client tokens with proxy service account
        # (only if integrations use shared credentials)
        # proxy_set_header Authorization "Bearer ${SF_SERVICE_TOKEN}";
    }
}

For body logging without OpenResty/Lua, use log_format with $request_body and $upstream_response_body (requires ngx_http_echo_module for response body capture).

Pattern B — Envoy with Credential Injection

Envoy Gateway (v1.3+, 2025) natively supports HTTPCredentialInjectionFilter, which injects an Authorization header into proxied requests. This is the right tool when:

  • Integrations use stale or per-user tokens
  • You want a single service-account credential at the proxy layer
  • You need mTLS between proxy and Salesforce
# EnvoyGateway HTTPRoute with credential injection
apiVersion: gateway.envoyproxy.io/v1alpha1
kind: HTTPRouteFilter
metadata:
  name: sf-credential-injector
spec:
  type: RequestHeaderModifier
  requestHeaderModifier:
    set:
      - name: Authorization
        value: "Bearer ${SF_PROXY_TOKEN}"
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: sf-proxy-route
spec:
  rules:
    - backendRefs:
        - name: salesforce-backend
          port: 443
      filters:
        - type: ExtensionRef
          extensionRef:
            group: gateway.envoyproxy.io
            kind: HTTPRouteFilter
            name: sf-credential-injector

Pattern C — Purpose-Built API Gateway with Request Transformation

When the custom CRM uses a different URL scheme or slightly different field names, a transformation layer sits between the integration and the custom CRM. This is the “shim” pattern used during phased migration.

┌──────────────────────────────────────────────────────────────────┐
│                PATTERN C: TRANSFORMATION GATEWAY                 │
│                                                                  │
│  Integration    API Gateway / Shim           Custom CRM         │
│  (speaks SF ──► ┌────────────────────────┐ ──► ┌─────────────┐ │
│   REST API)     │                        │     │  (Postgres   │ │
│                 │ SF Request →           │     │   backed,    │ │
│                 │   Normalize →          │ ◄── │   REST/GQL)  │ │
│                 │     Route to CRM       │     └─────────────┘ │
│                 │                        │                       │
│                 │ CRM Response →         │                       │
│                 │   Transform to SF      │                       │
│                 │     Response shape     │                       │
│                 └────────────────────────┘                       │
└──────────────────────────────────────────────────────────────────┘

This pattern is the most engineering-intensive but enables a true drop-in replacement. The transformation layer must handle:

  1. URL rewriting: /services/data/v60.0/sobjects/Account//api/objects/accounts/
  2. Field name mapping: Salesforce uses PascalCase field names with __c suffix for custom fields. Custom CRM may use snake_case.
  3. SOQL → SQL translation: The proxy must parse SOQL and emit equivalent queries against the custom CRM’s query endpoint.
  4. Response envelope normalization: Salesforce responses include attributes objects with type and url; custom CRMs typically omit these.
  5. Error code mapping: Salesforce error codes (REQUIRED_FIELD_MISSING, DUPLICATE_VALUE, etc.) must map to equivalent responses.

2.4 Record/Replay Validation Workflow

This is the definitive approach for validating custom CRM compatibility before cutover.

Step 1 — Instrument Production Proxy

Deploy Pattern A proxy in front of production Salesforce. Let it run for 2–4 weeks to capture representative traffic across all integrations and usage patterns. Capture:

  • Method, URL (full path + query string)
  • Request headers (sanitize auth tokens in storage)
  • Request body (JSON/CSV)
  • Response status code
  • Response headers
  • Response body
  • Latency
  • Timestamp

Store as JSONL (one record per request/response pair). A medium-sized org generates 10,000–100,000 API calls/day.

Step 2 — Build Replay Corpus

Filter the raw log to a canonical test corpus:

  • Remove health checks and metadata-only requests
  • Deduplicate by (method, path-template, response-status) — keep examples of each unique pattern
  • Anonymize PII in request/response bodies (replace real names, emails, SSNs with generated fixtures)
  • Tag by integration source (ETL pipeline vs. marketing tool vs. custom app)

Target size: 500–5,000 request/response pairs covering all object types and operations.

Step 3 — Tool Options for Replay

Hoverfly (preferred for high-volume, Go-native):

# Import recorded traffic
hoverfly --import recorded-sf-traffic.json

# Switch to simulate mode
hoverctl mode simulate

# Run integrations against Hoverfly → validates they get SF-compatible responses
# Then re-run against custom CRM and diff responses

Hoverfly’s “diff” mode captures deviations between expected (recorded from SF) and actual (from custom CRM) response. This is the diff report used to drive implementation.

WireMock (preferred for Java shops):

# Start WireMock in proxy+record mode
java -jar wiremock-standalone.jar \
  --proxy-all="https://your-org.my.salesforce.com" \
  --record-mappings \
  --port 8080

# Mappings are saved to /mappings/*.json and /__files/*.json
# Re-run integrations in stub mode against custom CRM
java -jar wiremock-standalone.jar --port 8080

WireMock supports request templating, which enables a single recorded mapping to match multiple record IDs (via regex/JsonPath matching) rather than requiring per-ID stubs.

Custom JSONL Replayer (for teams who want full control): A ~200-line Python or Node script that reads the JSONL corpus, replays each request against the target CRM, and diffs the response. This is the right choice when you need:

  • Custom diffing rules (e.g., ignore lastModifiedDate fields)
  • Integration with your test framework (pytest, Jest)
  • CI/CD pipeline integration
# Skeleton: sf_replay.py
import json, requests, deepdiff

def replay_corpus(corpus_path: str, target_base_url: str, ignore_fields: list[str]):
    results = {"pass": 0, "fail": 0, "diffs": []}
    with open(corpus_path) as f:
        for line in f:
            rec = json.loads(line)
            resp = requests.request(
                method=rec["method"],
                url=target_base_url + rec["path"],
                params=rec.get("query_params"),
                headers=sanitize_headers(rec["request_headers"]),
                json=rec.get("request_body"),
                timeout=10,
            )
            diff = deepdiff.DeepDiff(
                rec["response_body"],
                resp.json(),
                exclude_paths=[f"root['{f}']" for f in ignore_fields],
            )
            if diff:
                results["fail"] += 1
                results["diffs"].append({"request": rec["path"], "diff": str(diff)})
            else:
                results["pass"] += 1
    return results

Step 4 — Compatibility Scoring

Run the replay corpus against the custom CRM. Score by:

Metric Target
HTTP status code match rate 100%
Response body structural match (all keys present) 100%
Response body value match (non-volatile fields) >98%
Bulk API job completion within 2x SF latency >95%
OAuth token exchange round-trip 100%
SOQL pagination (multi-page queries) 100%

Volatile fields (timestamps, generated IDs) should be excluded from value-match scoring.


2.5 Salesforce OpenAPI Spec: Status and Generation

Salesforce entered beta on OpenAPI 3.0 spec generation for the sObjects REST API in Spring '21 (API v51.0) and has iterated since. As of Summer '25 (API v64.0):

  • Generation endpoint: GET /services/data/v64.0/openapi/3.0
  • Scope: Covers only the sObjects REST API (CRUD + describe endpoints). Does NOT cover: SOQL/SOSL endpoints, Bulk API 2.0, Streaming API, Composite/Batch, Pub/Sub API.
  • Org-specific: The generated spec reflects the actual objects and fields in your org (including custom objects). It is not a generic Salesforce spec.
  • Permissions required: Modify All Data OR Customize Application.

Practical use:

# Generate org-specific OpenAPI spec (requires valid access token)
curl -H "Authorization: Bearer $SF_TOKEN" \
  "https://your-org.my.salesforce.com/services/data/v64.0/openapi/3.0" \
  -o salesforce-org-openapi.json

# Validate your custom CRM against it using OpenAPI conformance tools
npx @schemathesis/schemathesis run \
  salesforce-org-openapi.json \
  --base-url http://localhost:8080 \
  --auth-type bearer \
  --auth $CUSTOM_CRM_TOKEN

Gap: No official OpenAPI spec exists for the SOQL query endpoint, Bulk API 2.0, Streaming API, or Pub/Sub API. These must be implemented from documentation, not a machine-readable spec.


2.6 SOQL → SQL Translation

SOQL is SQL-shaped but has Salesforce-specific extensions that require translation when building a compatible query layer.

SOQL Feature SQL Equivalent Notes
SELECT Id, Name FROM Account SELECT id, name FROM account Trivial; field name casing normalization needed
WHERE CreatedDate > 2024-01-01T00:00:00Z WHERE created_date > '2024-01-01' Salesforce date literals (LAST_N_DAYS, THIS_YEAR, etc.) need special handling
LIMIT 100 OFFSET 200 LIMIT 100 OFFSET 200 Direct; same semantics
ORDER BY Name ASC NULLS LAST ORDER BY name ASC NULLS LAST PostgreSQL supports NULLS LAST natively
SELECT Id, (SELECT Id FROM Contacts) FROM Account LEFT JOIN or subquery Salesforce relationship queries (child → parent, parent → child) require join logic
SELECT Account.Name FROM Contact SELECT a.name FROM contact c JOIN account a ON c.account_id = a.id Parent relationship traversal; dot notation maps to FK joins
WHERE Name LIKE 'Acme%' WHERE name LIKE 'Acme%' Direct
WHERE Id IN ('001...', '002...') WHERE id IN ('001...', '002...') Direct
SELECT COUNT() FROM Account SELECT COUNT(*) FROM account COUNT() with no args is Salesforce-specific
WHERE Name = null WHERE name IS NULL Salesforce uses = null, SQL uses IS NULL
USING SCOPE mine WHERE owner_id = current_user_id() Scope filters require current user context injection
WITH SECURITY_ENFORCED (RBAC check in middleware) Enforces field/object FLS; no SQL equivalent, must be handled in app layer

SOQL date literals are the most common pain point:

TODAY           → CURRENT_DATE
YESTERDAY       → CURRENT_DATE - INTERVAL '1 day'
LAST_7_DAYS     → CURRENT_DATE - INTERVAL '7 days'
LAST_N_DAYS:30  → CURRENT_DATE - INTERVAL '30 days'
THIS_MONTH      → DATE_TRUNC('month', CURRENT_DATE)
LAST_MONTH      → DATE_TRUNC('month', CURRENT_DATE) - INTERVAL '1 month'
THIS_QUARTER    → (requires quarter arithmetic)
FISCAL_YEAR     → (requires org-specific fiscal year config)

A production-grade SOQL parser exists: jsforce (JavaScript, open-source) parses SOQL into an AST. From the AST, generating PostgreSQL SQL is straightforward. Python option: simple-salesforce does not parse SOQL, but antlr4-soql (community) provides a grammar-based parser.


2.7 Practical Compatibility Checklist

Use this checklist before declaring a custom CRM “Salesforce API compatible” for a specific integration.

AUTHENTICATION
[ ] OAuth 2.0 Web Server Flow (authorization_code grant)
[ ] OAuth 2.0 JWT Bearer Flow
[ ] OAuth 2.0 Username-Password Flow (legacy support)
[ ] OAuth 2.0 Refresh Token grant
[ ] Token introspection endpoint
[ ] /services/oauth2/userinfo endpoint returns {sub, user_id, organization_id}
[ ] Instance URL in token response matches proxy base URL

SOBJECTS REST API
[ ] GET /services/data/ → returns [{version, url}] array
[ ] GET /services/data/v{n}/sobjects/ → returns {sobjects: [...]} with all objects
[ ] GET /services/data/v{n}/sobjects/{Object}/describe → full field metadata
[ ] POST /services/data/v{n}/sobjects/{Object}/ → returns {id, success, errors}
[ ] GET /services/data/v{n}/sobjects/{Object}/{id} → record with attributes object
[ ] PATCH /services/data/v{n}/sobjects/{Object}/{id} → 204 No Content on success
[ ] DELETE /services/data/v{n}/sobjects/{Object}/{id} → 204 No Content on success
[ ] PATCH upsert by external ID field
[ ] GET /updated?start=...&end=... → {ids, latestDateCovered}
[ ] GET /deleted?start=...&end=... → {deletedRecords, earliestDateAvailable, latestDateCovered}

QUERY
[ ] GET /services/data/v{n}/query/?q=... → {totalSize, done, records}
[ ] Pagination via nextRecordsUrl → GET /services/data/v{n}/query/{queryLocator}
[ ] GET /services/data/v{n}/queryAll/?q=... (includes deleted/archived)
[ ] SOQL date literals (TODAY, LAST_N_DAYS:n, THIS_MONTH, etc.)
[ ] SOQL relationship queries (child-to-parent dot notation)
[ ] SOQL subqueries (parent-to-child)
[ ] SOQL aggregate functions (COUNT, SUM, MAX, MIN, AVG)
[ ] SOQL GROUP BY and HAVING
[ ] SOQL ORDER BY NULLS FIRST/LAST

COMPOSITE / BATCH
[ ] POST /composite/ → up to 25 subrequests, referenceId chaining
[ ] POST /composite/batch → independent subrequests, allOrNone flag
[ ] POST /composite/sobjects → up to 200 records create/update
[ ] POST /composite/tree/{Object} → nested parent + children insert

BULK API 2.0
[ ] Create ingest job (insert, update, upsert, delete, hardDelete)
[ ] Upload CSV in batches
[ ] Close job → triggers async processing
[ ] Poll job status → (Open, UploadComplete, InProgress, JobComplete, Failed, Aborted)
[ ] Download successfulResults, failedResults, unprocessedRecords as CSV
[ ] Create query job → async SOQL
[ ] Download query results as CSV

ERROR RESPONSES
[ ] 400 with [{message, errorCode, fields}] array for validation errors
[ ] Standard Salesforce error codes (REQUIRED_FIELD_MISSING, DUPLICATE_VALUE,
    FIELD_CUSTOM_VALIDATION_EXCEPTION, INSUFFICIENT_ACCESS_ON_CROSS_REFERENCE_ENTITY)
[ ] 401 with {error: "INVALID_SESSION_ID", message: "..."} for auth failures
[ ] 404 with [{errorCode: "NOT_FOUND", message: "..."}]

STREAMING / EVENTS (if CDC consumers exist)
[ ] CometD handshake at /cometd/{version}/
[ ] PushTopic subscription (/topic/{name})
[ ] Platform Event subscription (/event/{Event__e})
[ ] Change Data Capture subscription (/data/{Object}ChangeEvent)
[ ] Pub/Sub API gRPC endpoint (if modern CDC consumers)

2.8 Tools Reference

Tool Role Language Notes
Hoverfly Record/replay proxy Go Best for high-volume traffic capture; diff mode for comparison
WireMock Record/replay + stub server Java Best for Java integration test suites; rich request matching
Prism (Stoplight) OpenAPI mock + validation proxy Node Validates requests/responses against OpenAPI spec in real-time
Schemathesis OpenAPI conformance testing Python Generates test cases from OpenAPI spec; fuzzes edge cases
jsforce Salesforce API client + SOQL parser Node/JS Use for SOQL AST generation in a Node-based proxy
simple-salesforce Salesforce API client Python Use for scripting replay corpus construction
nginx + OpenResty Transparent proxy + Lua scripting C/Lua Body capture requires Lua; production-grade
Envoy Gateway Credential injection + routing Go Native HTTPCredentialInjectionFilter in v1.3+
Kong API gateway with transformation plugins Lua/Go Request/response transform plugins; commercial plugins available
Caddy Reverse proxy with simple config Go Simpler than nginx for basic pass-through; limited body logging
Twenty CRM Custom CRM target TypeScript REST + GraphQL APIs; Postgres-backed; best open-source candidate for SF drop-in

2.9 Architecture Recommendation for Custom CRM Drop-In

If the goal is a custom Postgres-backed CRM that accepts Salesforce REST API calls transparently, the recommended architecture is:

┌─────────────────────────────────────────────────────────────────┐
│            RECOMMENDED DROP-IN REPLACEMENT ARCHITECTURE          │
│                                                                  │
│  Phase 1 (Record)        Phase 2 (Build)       Phase 3 (Cutover)│
│                                                                  │
│  All integrations        Build custom CRM      Flip DNS:         │
│       │                  with SF-compatible    integrations now  │
│       ▼                  API layer.            hit custom CRM    │
│  Hoverfly proxy   ──►    Run Hoverfly replay   directly.        │
│  in front of SF          corpus against CRM.                    │
│  (record mode)           Fix diffs.            Monitor replay    │
│       │                                        corpus score      │
│       ▼                  Target: >99% pass     continuously.     │
│  JSONL corpus            rate before cutover.                   │
│  (2-4 weeks of                                                   │
│   real traffic)                                                  │
└─────────────────────────────────────────────────────────────────┘

For teams building on Twenty CRM:

  • Twenty’s REST API is at /api/ (not /services/data/v{n}/), so an adapter layer is required
  • The Twenty GraphQL API at /api/graphql can serve as the back-end for a Salesforce-REST adapter
  • Recommended adapter stack: Node/Fastify shim that translates SF REST requests → Twenty GraphQL mutations/queries
  • SOQL translation: parse with jsforce AST → emit Twenty GraphQL or raw Postgres SQL via Twenty’s metadata API

The shim approach is the right call for Twenty because:

  1. Twenty’s object model (metadata-driven, arbitrary custom objects) maps cleanly to Salesforce’s sObject model
  2. The ID format differs (Twenty uses UUIDs, Salesforce uses 15/18-char base-62 IDs) — the shim handles ID translation
  3. Twenty’s relationship model (FK with metadata-defined edges) maps to Salesforce’s relationship fields

Summary Reference Tables

Cost Comparison at 50 Users (Annual)

Platform License Cost Infrastructure Est. Total
Salesforce Sales Cloud Enterprise $99,000 $0 (SaaS) $99,000–$180,000+
HubSpot Sales Hub Pro $60,000 $0 (SaaS) $60,000
Pipedrive Professional $35,400 $0 (SaaS) $35,400
Zoho CRM Enterprise $24,000 $0 (SaaS) $24,000
Twenty CRM (Cloud) $5,400 $0 (managed) $5,400
Twenty CRM (Self-hosted) $0 $2,000–$8,000/yr $2,000–$8,000
SuiteCRM (Self-hosted) $0 $2,000–$6,000/yr $2,000–$6,000

License cost only. Does not include admin labor, integration costs, or training.

Open Source CRM Comparison

Tool Stack API Stars (2026) Best For
Twenty CRM TypeScript/NestJS/React/Postgres REST + GraphQL 44,000+ Engineering-led teams, modern UX
SuiteCRM PHP 8/MySQL REST (limited) ~4,000 Feature parity with SF, enterprise modules
Erxes TypeScript/Node/MongoDB GraphQL ~15,000 Unified sales + marketing + support
Odoo CRM Python/Postgres JSON-RPC ~35,000 ERP-integrated, broad module ecosystem

Proxy Tool Decision Guide

Scenario Recommended Tool
Capture production traffic corpus without code changes Hoverfly (proxy mode) or nginx + OpenResty
Java integration test suite with rich stub matching WireMock
OpenAPI spec conformance validation Schemathesis + Prism
Credential injection at network layer (K8s) Envoy Gateway
Request/response transformation gateway Kong with transform plugin
Simple Node.js shim adapter (SF REST → custom CRM) Fastify + jsforce SOQL parser

Sources