Documentation Index
Fetch the complete documentation index at: https://docs.dualentry.com/llms.txt
Use this file to discover all available pages before exploring further.
Salesforce Integration: OAuth, settings, and revenue objects
Connect Salesforce to DualEntry so business accounts, contacts, products, and opportunities at a configured stage flow into DualEntry as customers, items, and draft revenue contracts. Each opportunity line item becomes a performance obligation on the resulting contract, which the rev rec engine picks up after you activate the contract manually. At this point, the connector only syncs from Salesforce into DualEntry. It does not write any information back into Salesforce. DualEntry uses Salesforce’s OAuth 2.0 authorization-code flow againstlogin.salesforce.com, stores and refreshes credentials in compliance with the connected org’s session policy. The minimum version of the Salesforce server REST API supported is 58.0.
Prerequisites
Confirm the following before connecting Salesforce:- Admin access in DualEntry with permission to manage integrations.
- Permission in your Salesforce org to authorize OAuth apps (System Administrator, or a profile with “API Enabled” and Connected App access).
- The DualEntry Connected App installed in your Salesforce org. Salesforce has required Connected Apps to be packaged and installed in the customer’s org before OAuth succeeds since the September 2025 platform change; this applies to every edition. DualEntry publishes the package; your Salesforce admin installs it and approves the
apiandrefresh_tokenscopes before you connect. - The DualEntry company that will own synced contracts and items already exists in DualEntry. Sync writes to a single accounting entity per Salesforce integration; consolidation across entities is handled by your existing multi-entity setup, not by the connector.
- A chart of accounts that already contains the GL accounts you will pick in integration settings (accounts receivable, deferred revenue, income, expense).
- Agreement with your revenue team on which Salesforce opportunity stage triggers sync into DualEntry. The default is
Closed Won, and the trigger-stage dropdown is populated at connect time from your org’s active opportunity stages. - Active products tied to the Standard Pricebook in Salesforce. The connector imports
Product2records that have at least onePricebookEntryon the Standard Pricebook; products without one are skipped.
Step 1: Connect with OAuth
DualEntry uses the OAuth 2.0 authorization-code flow against Salesforce’s standard auth host.- Navigate to Company → Integrations, find Salesforce and click Connect.
- Approve the DualEntry app in Salesforce. Salesforce will redirect to DualEntry’s callback URL with the authorization.
- DualEntry exchanges credentials from Salesfoce, as well as the per-org
instance_url, and validates that the REST API version your org advertises that meets or exceeds58.0.
UNSUPPORTED_API_VERSION. You can still reconnect after your Salesforce admin restores a supported release. Salesforce Developer Edition orgs also authenticate through login.salesforce.com.
For platform outages, see Salesforce Trust. For details about the Salesforce API behavior, see the Salesforce REST API Developer Guide.
Step 2: Configure required integration settings
These parameters configure the synchronization process to know which DualEntry company owns the imported records and which GL accounts to apply to new items. Sync will not produce clean records until every required setting resolves to a valid record.| Setting | Purpose |
|---|---|
| Company (accounting entity) | The DualEntry company that owns synced contracts and items. |
| Accounts receivable account | AR account on synced customers; used when invoice writeback ships in a later release. |
| Deferred revenue account | Deferred revenue account applied to service-type items so obligations defer correctly. |
| Income account | Default income account on items created from Salesforce products. |
| Expense account | Default expense account on the same items. |
| Opportunity trigger stage | Salesforce opportunity stage that promotes it into a draft contract. The default is Closed Won. The dropdown lists your org’s active stages. |
salesforce_api_version setting is detected at connect time and not user-editable and records the negotiated REST API version.
The historical sync window defaults to 90 days through the salesforce_historical_sync_days setting. Sync uses that window only on the first run for each record type. Subsequent syncs honor the per-record-type LastModifiedDate watermark described under Incremental sync.
Step 3: Run sync
Trigger a sync from Company → Integrations → Salesforce → Sync. See the next section for details on the synchronized entities.What syncs into DualEntry
The connector imports four Salesforce object types, each mapping to a specific DualEntry record. Sections below describe how each object resolves and how subsequent syncs update it.Products to items
SalesforceProduct2 records with at least one Standard Pricebook entry become DualEntry items. The item carries the Salesforce product name, description, and a SKU derived from ProductCode. When a product has no ProductCode, the Salesforce record ID is used as the SKU instead. Income, expense, and deferred revenue accounts come from the values you set in Step 2.
Accounts to customers
Each Salesforce business account becomes a company-type DualEntry customer. Billing address, phone, and website propagate from the account when present. Person Accounts are excluded from V1 and do not sync; contacts belonging to a Person Account are also skipped on the contact pull.Contacts to individual customers
Each SalesforceContact whose AccountId matches a synced account becomes an individual-type DualEntry customer with email, phone, and mailing address. DualEntry’s customer model is flat (no separate contact entity under a parent customer), so contacts that share an account do not collapse into a single customer record; each contact is its own individual customer. The contact pull is scoped to WHERE AccountId IN (...) so unrelated contacts do not import.
Opportunities to draft contracts
Each opportunity at the configured trigger stage becomes a draft contract:- Customer resolves from
AccountIdvia the account integration record. If the account has not been synced or has no DualEntry customer mapping, the opportunity is flagged withMISSING_ACCOUNT_MAPPING. - Contract date and start date are both set to
Opportunity.CloseDatein V1. Custom-field overrides ship in a later release. - Currency is the configured company’s default currency.
- Performance obligations are built one per
OpportunityLineItem, withquantityfromQuantity,ratefromUnitPrice, andstandalone_selling_priceset torate * quantity. Recognition strategy defaults to immediate; pick the final strategy during contract activation. - Item on each obligation resolves from
Product2Idvia the product integration record. Lines that reference a product without a mapping surface asMISSING_PRODUCT_MAPPING.
Incremental sync
After the first full sync, each subsequent run pulls only records modified since the last successful sync. The watermark is computed per source type as the maximumLastModifiedDate across previously synced records of that type, using Salesforce’s own timeline rather than DualEntry’s local clock. This avoids a gap where records modified during a previous sync’s fetch window would be missed by a clock-based watermark.
Salesforce does not propagate Opportunity.LastModifiedDate when only a child OpportunityLineItem changes. The connector compensates by issuing a second query for line items modified since the watermark, then fetching any parent opportunities not already covered by the first query. The result is the same as if Salesforce supported a single query with a semi-join, but split into two steps because SOQL rejects that syntax.
The first synchronization for each source type fetches using 90 days as the default watermark.
Once the watermark identifies which opportunities to refresh, the connector pulls the full current set of OpportunityLineItem rows for those opportunities. Rebuilding each contract’s performance obligations requires the complete line-item state, including deletions, so a removed Salesforce line still flows through and updates the draft contract.
Current limitations
Several aspects of the connector in its current version are worth knowing:- One-way only. DualEntry does not push contracts, customers, items, or accounting state into Salesforce. There is no writeback path.
- Person Accounts excluded. the connector filters to
IsPersonAccount = false. Contacts whose parent is a Person Account do not sync either. - Stage regression does not unwind. Once an opportunity has been promoted to a draft contract, demoting it back to a non-trigger stage in Salesforce does not delete or archive the draft contract. Cancel the draft contract manually in DualEntry if needed.
- Custom-field mapping not yet supported. Mapping Salesforce custom fields on opportunities or line items to DualEntry classifications or custom fields is on the roadmap. V1 supports standard
OpportunityandOpportunityLineItemfields only. - Line items drive contract value.
Opportunity.Amountis logged as a warning if it diverges from the sum of the total prices from allOpportunityLineItem, but the contract is still built from line items. - Standard Pricebook only. Products without a
PricebookEntryon the Standard Pricebook are skipped. - Rate limits enforced per Salesforce org. Sync paces requests against your org’s daily API allocation and pauses when usage crosses 95% of the allocation to accommodate for higher priority interactions.
Troubleshoot sync errors
When a record fails, the error appears in the Integration Errors log under Settings → Integrations → Salesforce. Per-record codes are stored verbatim onIntegrationRecord.import_error; integration-wide failures (authentication, API version negotiation, rate-limit trip) set Integration.status = ERROR with a status message on the integration itself instead. The most common cases:
| Code or symptom | Cause | Resolution |
|---|---|---|
MISSING_ACCOUNT_MAPPING | The opportunity’s Salesforce account has no DualEntry customer mapping. | Run a full sync so the account syncs first; if the account is a Person Account, switch it to a business account or skip the opportunity. |
MISSING_PRODUCT_MAPPING | An opportunity line item references a Product2 with no DualEntry item. | Verify the Salesforce product is active and has a Standard Pricebook entry, then re-run sync. |
MISSING_PRODUCT_ON_LINE_ITEM | An OpportunityLineItem has no Product2Id. | Attach a product to the line in Salesforce, or remove the line. |
MISSING_LINE_ITEMS | The opportunity has zero OpportunityLineItem rows. | Add at least one line item in Salesforce, or move the opportunity out of the trigger stage. |
MISSING_COMPANY | The Company setting is empty or points at a deleted DualEntry company. | Open Settings → Integrations → Salesforce → Settings and pick a valid company. |
MISSING_INCOME_ACCOUNT, MISSING_EXPENSE_ACCOUNT, MISSING_DEFERRED_REVENUE_ACCOUNT | The matching GL account setting is empty or invalid. | Pick a valid GL account in integration settings. |
UNSUPPORTED_API_VERSION (integration-level) | The Salesforce org exposes no REST API version at or above the connector minimum. The status is set on the integration, not on individual records. | Ask your Salesforce admin to confirm the org is on a supported release, then reconnect. |
| ”Salesforce daily request limit reached.” (integration-level) | The org’s daily API allocation reached the 95% trip threshold; internally logged as RATE_LIMIT_BREAKER_OPEN. | Wait for the cooldown to elapse; investigate whether other tools are competing for the same allocation. |
| OAuth fails immediately | Mismatched redirect URI, missing or unapproved Connected App, or the user lacks “API Enabled”. | Verify the Connected App is installed and approved in your org, check the redirect URI matches the deployment, and confirm the user’s profile allows API access. |
| Repeated 401 mid-sync | Refresh token revoked or session policy tightened in Salesforce. | Reconnect OAuth in DualEntry to obtain new tokens. |
