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 | |
| 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:
- Configure Salesforce Change Data Capture (CDC) or Platform Events to send changes to your middleware
- Your middleware transforms and pushes data to Propstreet via our API
- 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)
- Register a Propstreet webhook for
contact.*andcompany.*events - Propstreet pushes changes to your endpoint in real-time
- Your middleware transforms and pushes to Salesforce using External ID upsert
Option B: Polling
- Your integration layer polls the Propstreet API for changes (using
updated_since) - Changes are transformed and pushed to Salesforce via their REST API
- 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:
-
Create External ID fields in Salesforce:
Propstreet_Contact_Id__c(Text, External ID, Unique)Propstreet_Company_Id__c(Text, External ID, Unique)
-
Store Salesforce IDs in Propstreet's
externalRefs:{ "externalRefs": [{ "namespace": "salesforce", "id": "003xx000001234ABC" }] } -
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__cas External ID (unique) on Contact - Create
Propstreet_Company_Id__cas External ID (unique) on Account
In Propstreet:
- Use
externalRefswith 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.AccountIdfor 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_initiatorfield 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:
- Salesforce Account — Professional or higher recommended; Enterprise+ for Change Data Capture
- Connected App — Configure OAuth 2.0 for server-to-server authentication (JWT bearer flow recommended)
- Propstreet API Credentials — Bot User + Client Credentials for production
- Integration Layer — Middleware to handle events and API calls (MuleSoft, Azure Logic Apps, n8n, or custom)
- 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
- Generate Propstreet API credentials in your account settings (requires login)
- Create a Connected App in Salesforce Setup and configure OAuth
- Create External ID custom fields on Contact and Account
- Review the Propstreet API documentation for endpoints and data models
- Set up your integration layer to sync data between Salesforce and Propstreet
- If using real-time sync, configure Salesforce CDC and/or Propstreet webhooks
- 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) andLastModifiedDate(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.,
PersonEmailvsEmail)
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
statusis 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 systemclassification: Work stage (draft,active,inactive) — settable via APIteaser.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):
- Enable Change Data Capture for Opportunity and OpportunityContactRole
- Subscribe to
/data/OpportunityChangeEventin your middleware - Transform and push to Propstreet Projects API
- 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):
- Register webhooks for
project.*andprospect.*events - Look up Salesforce ID from
externalRefs - 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
- Propstreet Contacts API
- Propstreet Companies API
- Propstreet Projects & Prospects Guide
- Propstreet Webhooks Guide
- Salesforce REST API
- Salesforce Contact sObject
- Salesforce Account sObject
- Salesforce Opportunity sObject
- Salesforce OpportunityContactRole sObject
- Salesforce Change Data Capture
- Salesforce External ID Upsert