Salesforce Integration

Sync your Salesforce CRM contacts and accounts with Propstreet automatically.

Overview

Feature Support
Market Global
Sync Direction Bidirectional
Real-time Updates Yes (customer-managed sync)
Contacts ✅ Full support
Companies ✅ Full support
Projects ✅ Full support (v1.2.0)
Prospects ✅ Full support (v1.2.0)
Properties ⚠️ API available (v1.3.0)

Data Mapping

Contact Fields

Salesforce Propstreet Notes
FirstName First Name
LastName Last Name
Email Email Primary email synced
MobilePhone / Phone Phone Mobile preferred
Title Job Title Synced to company connection
AccountId Connection Links contact to company
Propstreet_Contact_Id__c ID External ID field (create this)
Custom: Investor Strategy Strategy Investment focus (custom field)

Note: Salesforce supports multiple phone fields. We recommend syncing MobilePhone to Propstreet's phone field; if mobile is empty, use the main Phone.

Account Fields (Company)

Salesforce Propstreet Notes
Name Name
Website Homepage URL Full URL synced
Domain Set directly or auto-extracted from URL
BillingCountry Country Code Map to ISO 2-letter code
Propstreet_Company_Id__c ID External ID field (create this)
Custom: Organization Number External Reference For Nordic deduplication

How Sync Works

Salesforce → Propstreet

You control the sync through your own integration layer:

  1. Configure Salesforce Change Data Capture (CDC) or Platform Events to send changes to your middleware
  2. Your middleware transforms and pushes data to Propstreet via our API
  3. Records are matched using email, domain, or External ID and created/updated in Propstreet

Salesforce CDC provides near real-time change notifications for create, update, delete, and undelete events.

Propstreet → Salesforce

When you update a contact in Propstreet, you have two options:

Option A: Webhooks (Recommended)

  1. Register a Propstreet webhook for contact.* and company.* events
  2. Propstreet pushes changes to your endpoint in real-time
  3. Your middleware transforms and pushes to Salesforce using External ID upsert

Option B: Polling

  1. Your integration layer polls the Propstreet API for changes (using updated_since)
  2. Changes are transformed and pushed to Salesforce via their REST API
  3. Your Salesforce records stay in sync

Propstreet recommends polling every 5–15 minutes for delta sync.

Duplicate Handling

Salesforce's External ID upsert is the most reliable way to prevent duplicates:

  1. Create External ID fields in Salesforce:

    • Propstreet_Contact_Id__c (Text, External ID, Unique)
    • Propstreet_Company_Id__c (Text, External ID, Unique)
  2. Store Salesforce IDs in Propstreet's externalRefs:

    {
      "externalRefs": [{ "namespace": "salesforce", "id": "003xx000001234ABC" }]
    }
    
  3. Use Salesforce upsert by External ID:

    PATCH /services/data/v62.0/sobjects/Contact/Propstreet_Contact_Id__c/{propstreetId}
    

This approach ensures deterministic matching without relying on name or email alone.

Field Considerations

External ID Strategy

Store cross-system IDs to enable reliable bidirectional sync:

In Salesforce:

  • Create Propstreet_Contact_Id__c as External ID (unique) on Contact
  • Create Propstreet_Company_Id__c as External ID (unique) on Account

In Propstreet:

  • Use externalRefs with namespace "salesforce" containing the Salesforce record ID

Multi-Company Relationships

Commercial real estate investors often have multiple company affiliations (fund manager, board member, personal investments). Propstreet models this as Links with their own API endpoints.

For Salesforce, you have two options:

Option A: Primary Company Only (Simpler)

  • Use the standard Contact.AccountId for the primary company
  • Accept that secondary affiliations won't sync

Option B: Full Relationship Fidelity (Recommended)

  • Create a custom junction object like Investor_Affiliation__c:
    • Contact__c (Lookup to Contact)
    • Account__c (Lookup to Account)
    • Job_Title__c (Text)
    • Is_Primary__c (Checkbox)
    • Propstreet_Link_Id__c (External ID)
  • Sync Propstreet Links to this object

Notes vs Strategy

Propstreet has two different fields for text information:

  • Strategy — Investment focus (e.g., "Office properties, €10–50M, Nordics")
  • Notes — Activity log entries (call notes, meeting summaries)

Create a custom Salesforce field for Investor_Strategy__c to separate investment criteria from general notes and activity history.

Phone Numbers

Salesforce has separate fields for Phone, MobilePhone, HomePhone, etc. We recommend:

  • Sync MobilePhone to Propstreet's phone field
  • If MobilePhone is empty, fall back to Phone
  • Other phone fields remain in Salesforce only

Organization Number (Nordic)

Salesforce doesn't have a built-in organization number field. For Nordic markets, create a custom field on Account to store:

  • Norway: Organisasjonsnummer
  • Sweden: Organisationsnummer
  • Denmark: CVR-nummer
  • Finland: Y-tunnus

Use this with Propstreet's externalRefs for accurate company matching.

Conflict Resolution

For bidirectional sync, design your integration to handle conflicts. Recommended approaches:

Field Ownership Matrix:

  • Identity fields (name, email, phone): Salesforce wins
  • Network enrichment (tags, strategy): Propstreet wins
  • Relationship data (primary company): Propstreet wins
  • Compliance/consent flags: Salesforce wins

Loop Prevention:

  • Check the change_initiator field in Propstreet webhooks to skip events your integration triggered
  • Filter Salesforce CDC events made by your integration user

Propstreet supports ETags for optimistic concurrency—use If-Match headers to prevent lost updates.

Setup Requirements

To build a Salesforce integration, you'll need:

  1. Salesforce Account — Professional or higher recommended; Enterprise+ for Change Data Capture
  2. Connected App — Configure OAuth 2.0 for server-to-server authentication (JWT bearer flow recommended)
  3. Propstreet API Credentials — Bot User + Client Credentials for production
  4. Integration Layer — Middleware to handle events and API calls (MuleSoft, Azure Logic Apps, n8n, or custom)
  5. Custom Fields — Create External ID fields and any additional fields (organization number, investor strategy)

Change Data Capture Note

Salesforce CDC requires Enterprise Edition or higher. Without CDC, you can integrate using:

  • Scheduled polling of Salesforce REST API
  • Apex triggers with outbound messages
  • Platform Events (with custom implementation)

Getting Started

  1. Generate Propstreet API credentials in your account settings (requires login)
  2. Create a Connected App in Salesforce Setup and configure OAuth
  3. Create External ID custom fields on Contact and Account
  4. Review the Propstreet API documentation for endpoints and data models
  5. Set up your integration layer to sync data between Salesforce and Propstreet
  6. If using real-time sync, configure Salesforce CDC and/or Propstreet webhooks
  7. Test with a small dataset before full rollout

You're responsible for building and maintaining the integration.

Frequently Asked Questions

Which Salesforce editions support the integration?

The integration works with all Salesforce editions. However:

  • Change Data Capture requires Enterprise Edition or higher
  • API limits vary by edition—higher tiers have more generous limits

How does Salesforce rate limiting work?

Salesforce uses concurrent and daily API call limits based on your edition and number of user licenses. For high-volume sync:

  • Use Composite API to batch up to 25 operations per call
  • Use sObject Collections to upsert up to 200 records per call
  • Use Bulk API 2.0 for initial loads (async, handles millions of records)

How often does data sync?

Sync frequency depends on how you configure your integration layer:

  • Real-time: Use Salesforce CDC + Propstreet webhooks for immediate syncs
  • Scheduled: Poll Propstreet API every 5–15 minutes (using updated_since)
  • Manual: Trigger syncs on-demand

What happens to data if I stop syncing?

Your data remains in both systems. No data is deleted. Use External ID fields and externalRefs to maintain record linkage if you resume syncing later.

Can I choose which contacts to sync?

Yes. Your integration layer controls which records to sync. Common filtering approaches:

  • Salesforce record types or custom fields
  • Reports or SOQL queries (e.g., only sync contacts with certain tags)
  • Account-based filtering (sync contacts linked to specific accounts)

How should I handle merge conflicts?

Design your integration layer to handle conflicts:

  • Last write wins: Compare updatedUtc (Propstreet) and LastModifiedDate (Salesforce)
  • Source of truth: Designate one system as authoritative for specific fields
  • Manual review: Flag conflicts for human review

Propstreet supports ETags for optimistic concurrency—use If-Match headers to prevent lost updates.

How do I handle Salesforce Person Accounts?

If your org uses Person Accounts:

  • Person Accounts map to Propstreet Contacts
  • Business Accounts map to Propstreet Companies
  • The mapping is conceptually similar, but field names differ slightly (e.g., PersonEmail vs Email)

Projects & Prospects (v1.2.0)

Sync real estate deals between Salesforce Opportunities and Propstreet Projects. Track investor prospects through your pipeline with bidirectional classification sync.

Opportunity to Project Mapping

Salesforce Opportunity Propstreet Project Notes
Id externalRefs namespace: "salesforce"
Name name Direct mapping
StageName Use for display; see status mapping below
(custom) Is_Portfolio__c isPortfolio Create custom checkbox field
(custom) Property_Count__c propertyCount Create custom number field
CreatedDate createdUtc Read-only in Propstreet
LastModifiedDate updatedUtc For delta sync

Note: Project status is read-only in Propstreet. It's auto-generated based on workflow state (whether teaser has been published, communicated, etc.). Use the mapping below for display purposes when syncing FROM Propstreet TO Salesforce.

OpportunityContactRole to Prospect Mapping

Salesforce OCR Propstreet Prospect Notes
Id externalRefs namespace: "salesforce" for OCR ID
ContactId contactId Via Contact external ref lookup
Role classification Map roles to classifications (see below)
IsPrimary Not directly supported; use capabilities

Stage Mapping

Propstreet uses three separate fields for project state. Map Salesforce opportunity stages to the appropriate combination:

Salesforce Stage (suggested) status classification teaser.stage Notes
Prospecting / Qualification open draft Project setup
Needs Analysis open active published Teaser published
Proposal/Quote / Negotiation open active communicated Teaser sent to investors
Closed Won closed Deal completed
Closed Lost deleted Project archived/cancelled
  • status: Lifecycle state (open, closed, deleted) — read-only, set by system
  • classification: Work stage (draft, active, inactive) — settable via API
  • teaser.stage: Teaser progress (property_added, drafting, published, verified, communicated) — read-only, set by system

Contact Role to Classification Mapping

Prospect classification is read-write and settable via API.

Salesforce Role Propstreet Classification Rationale
(not assigned) not_contacted Default state
Evaluator contacted Reviewing the deal
Influencer contacted Engaged but not deciding
Economic Buyer interested Financial decision maker
Decision Maker interested Primary decision authority
(custom) Bidder bidder Actively participating

Tip: Create a custom "Bidder" picklist value for OpportunityContactRole to map directly to Propstreet's bidder classification.

External ID Fields

Create these custom fields in Salesforce for reliable bidirectional sync:

On Opportunity:

  • Propstreet_Project_Id__c (Text, External ID, Unique)

On OpportunityContactRole:

  • Propstreet_Prospect_Id__c (Text, External ID, Unique)

Sync Architecture

Salesforce → Propstreet (using CDC):

  1. Enable Change Data Capture for Opportunity and OpportunityContactRole
  2. Subscribe to /data/OpportunityChangeEvent in your middleware
  3. Transform and push to Propstreet Projects API
  4. Store Propstreet IDs in custom External ID fields
// Handle Salesforce CDC event
async function handleOpportunityChange(event) {
  const { changeType, recordIds } = event.ChangeEventHeader;
  const sfOppId = recordIds[0];

  if (changeType === "CREATE") {
    const project = await propstreet.post("/api/v1/projects", {
      name: event.Name,
      externalRefs: [{ namespace: "salesforce", id: sfOppId }],
    });
    // Update Salesforce with Propstreet ID
    await salesforce.update("Opportunity", sfOppId, {
      Propstreet_Project_Id__c: project.id,
    });
  }
}

Propstreet → Salesforce (using Webhooks):

  1. Register webhooks for project.* and prospect.* events
  2. Look up Salesforce ID from externalRefs
  3. Upsert to Salesforce by External ID
// Handle Propstreet webhook
async function handlePropstreetWebhook(payload) {
  const sfId = payload.data.externalRefs?.find(
    (ref) => ref.namespace === "salesforce"
  )?.id;

  if (payload.event === "prospect.updated" && sfId) {
    const sfRole = mapClassificationToRole(payload.data.classification);
    await salesforce.update("OpportunityContactRole", sfId, { Role: sfRole });
  }
}

Webhook Events

Subscribe to these Propstreet events for deal sync:

Event Description Salesforce Action
project.created New project created Create Opportunity
project.updated Project fields modified Update Opportunity
project.deleted Project soft-deleted Close as Lost
prospect.created Investor added to prospect list Create OpportunityContactRole
prospect.updated Classification changed Update Role
prospect.deleted Investor deleted from prospect list Delete OpportunityContactRole

Example: Create Project with Salesforce Reference

curl -X POST "https://app.propstreet.com/api/v1/projects" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Stockholm Office Portfolio",
    "isPortfolio": true,
    "propertyCount": 5,
    "externalRefs": [{ "namespace": "salesforce", "id": "0064V00000abc123" }]
  }'

Example: Lookup Project by Salesforce ID

curl "https://app.propstreet.com/api/v1/projects/external/salesforce/0064V00000abc123" \
  -H "Authorization: Bearer $TOKEN"

API References