Why This Setup Lives in Intuit, Not Salesforce
Every QuickBooks to Salesforce integration starts in the same place, and it is not Salesforce. Before you can push an Invoice into QuickBooks Online or pull a Payment back into your org, Intuit needs to know exactly who is calling its APIs, which company file they may touch, and where to send the user after consent. All of that lives in an OAuth app in the Intuit Developer portal, and it must exist before you write a line of Apex.
Cloudsheer has shipped this integration across 8 client projects, and the failures cluster in the same three places: a redirect URI off by a trailing slash, sandbox keys pointed at the production host, and a realmId nobody wrote down. This tutorial is the zoomed-in first step of our full technical guide at cloudsheer.com/integrations/quickbooks/technical-guide. Follow it click by click and you finish with development keys, a sandbox company, a Salesforce Named Credential, and a passing smoke test.
What You Need Before You Start
- An Intuit account. Sign up at developer.intuit.com, or sign in with an existing Intuit login. A US QuickBooks Online sandbox company is created automatically with a new developer account, and the portal typically forces email and 2FA verification before you can manage apps.
- A Salesforce org with My Domain enabled and admin rights to create Auth Providers, External Credentials, Named Credentials, and permission sets.
- A password manager entry ready for the Client ID and Client Secret. Intuit can re-reveal the secret later, but it should never touch email, Slack, or a spreadsheet.
- A clear picture of the environment split: development keys work only with sandbox companies and https://sandbox-quickbooks.api.intuit.com, production keys only with live companies and https://quickbooks.api.intuit.com.
- About 45 minutes for the sandbox path. Production adds Intuit's app assessment questionnaire, measured in days, not minutes.
Step 1: Create the App in the Intuit Developer Portal
Sign in at developer.intuit.com. In the upper-right toolbar open My Hub, then App dashboard, then click Create an app.
- Choose QuickBooks Online and Payments as the platform, then name the app something environment-neutral like Acme Salesforce Integration. The name appears on the consent screen users see.
- On the Add permissions page tick Accounting, the scope string com.intuit.quickbooks.accounting. That single scope covers every v3 Accounting entity: Customer, Invoice, Payment, and the rest. Add Payments (com.intuit.quickbooks.payment) only if you process card charges.
- Click Create. The app opens with Development keys issued and the OAuth 2.0 Playground redirect URI pre-seeded.
Step 2: Copy Development Keys and Your Sandbox Company ID
Open the app in the App dashboard and work from the left navigation pane.
- Click Keys and credentials, then in the Development section toggle Show credentials. Copy the Client ID and Client Secret into your password manager. These are sandbox-only keys. Intuit can re-reveal the secret anytime and offers Rotate Secret, but rotating instantly breaks any Salesforce config holding the old value.
- Create a sandbox: My Hub > Sandboxes > Add > QuickBooks Online Plus or Advanced > pick a Country > Create. The country is locked at creation, you can hold at most 10 sandboxes, each lasting 2 years.
- Copy the Company ID on the new sandbox card. This is the realmId, used in every API path as /v3/company/{realmId}/. It never appears in a token response, so record it now in custom metadata.
Step 3: Create the Salesforce Auth Provider
Because users authorize through a browser consent screen, Salesforce needs an Auth Provider. In Setup, open Auth. Providers under Identity and click New.
- Provider Type: Open ID Connect. Give it a Name and URL Suffix, for example QuickBooks for both.
- Consumer Key = the Intuit Client ID. Consumer Secret = the Intuit Client Secret. Use the Development pair for now.
- Authorize Endpoint URL: https://appcenter.intuit.com/connect/oauth2. Token Endpoint URL: https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer. Default Scopes: com.intuit.quickbooks.accounting.
- Leave the PKCE checkbox unchecked. Intuit's discovery document advertises only client_secret_post and client_secret_basic, and typically no PKCE support.
- Click Save. Only after the first save does Salesforce generate the Callback URL, in the Salesforce Configuration section at the bottom: https://yourdomain.my.salesforce.com/services/authcallback/QuickBooks. Copy it exactly.
Step 4: Register the Callback URL in Intuit's Redirect URIs
Back in the Intuit portal: your app > Keys and credentials > Redirect URIs (Development section) > Add URI. Paste the Salesforce Callback URL verbatim and Save.
- Intuit matches redirect_uri byte for byte: protocol, host, port, path, trailing slash. One stray character and the authorize page shows 'The redirect_uri query parameter value is invalid. Make sure it is listed in the Redirect URIs section on your app's keys tab and matches it exactly.'
- HTTPS is mandatory for anything but localhost, and http://localhost works only in the Development section. The Salesforce callback is HTTPS and static, so it passes.
- Development and Production keep separate Redirect URI lists paired with their own keys, each typically capped at 25 URIs. You will register this same URL in the Production list at go-live.
Step 5: Create the External Credential, Principal, and Permission Set
In Setup, open Named Credentials, switch to the External Credentials tab, and click New.
- Authentication Protocol: OAuth 2.0. Authentication Flow Type: Browser Flow. Identity Provider: the Auth Provider from Step 3. Scope: com.intuit.quickbooks.accounting. Save.
- On the External Credential detail page, in the Principals section, click New. Name it QBO with Identity Type Named Principal, so every callout shares one org-wide QuickBooks connection.
- Create or edit a permission set: add External Credential Principal Access for the QBO principal and grant full CRUD on the User External Credentials object. Assign it to yourself and the integration user. Skip this and Authenticate fails silently.
Step 6: Authenticate the Principal and Create the Named Credential
- On the External Credential, open the QBO principal's row-actions dropdown arrow and click Authenticate. Disable pop-up blockers first, because the Intuit consent window opens in a new tab.
- Sign in with your developer account, pick your sandbox company on the consent screen, and click Connect. The principal's Authentication Status flips to Configured.
- Watch the address bar during the redirect: the callback carries code=, state=, and realmId= query parameters. Salesforce discards realmId, so if you skipped it in Step 2, capture it here.
- Then Setup > Named Credentials > Named Credentials tab > New: Name QBO_NC, URL https://sandbox-quickbooks.api.intuit.com, External Credential = the one above, Generate Authorization Header enabled. Save.
Step 7: Run the Smoke Test
Prove the chain with the standard QuickBooks health check, CompanyInfo. In Execute Anonymous, send a GET to callout:QBO_NC/v3/company/{realmId}/companyinfo/{realmId} with an Accept: application/json header, substituting your real realmId in both places.
- A 200 response containing your sandbox's CompanyName means keys, redirect URI, scope, tokens, and Named Credential are all correct. The tool-side setup is verified end to end.
- Access tokens last 60 minutes. When one expires QuickBooks returns a 401, Salesforce refreshes and retries. Refresh tokens roll on a 100-day window and rotate roughly daily, all handled by the External Credential.
- Do not append a minorversion below 75. Since August 1, 2025 QuickBooks serves every request as minorversion=75 and silently ignores lower values. Omit the parameter or pass 75.
Step 8: Unlock Production Keys and Go Live
Production is where Intuit gets strict, even for a private internal app that never appears on the QuickBooks App Store.
- In the app's Production section, Keys & credentials stays locked until you complete production settings: EULA URL, privacy policy URL, host domain, launch URL, disconnect URL, hosting countries and IP addresses. Since February 24, 2026 a Reconnect URL field is also mandatory.
- Submit the App Assessment Questionnaire, a legal, technical, security, and data-handling review, and wait for status Approved. Budget days, not hours. Keys still locked after approval is a known portal glitch; contact Intuit support.
- Repeat the Salesforce side with production values: a second Auth Provider and External Credential (or swap the key pair in place), register the same callback URL in the Production Redirect URIs list, point the Named Credential at https://quickbooks.api.intuit.com, and re-run Authenticate as an admin of the live company.
Common Errors and How to Fix Them
- 'The redirect_uri query parameter value is invalid' on Intuit's authorize page: the registered URI does not match byte for byte. Re-copy the Auth Provider Callback URL, check trailing slash and https, and confirm it is in the environment section matching your keys.
- invalid_client from the token endpoint: wrong or rotated Client Secret, or a dev secret sent to a production authorize. Re-copy both values from Keys and credentials and update the Auth Provider.
- HTTP 400 invalid_grant on refresh: the refresh token is stale (Intuit rotates the value roughly every 24 hours), past its 100-day window, or revoked. Re-run Authenticate on the External Credential principal.
- HTTP 401 message=AuthenticationFailed; errorCode=003200: the 60-minute access token expired. Salesforce auto-refreshes after this 401 and retries, so a one-off in logs is normal. A loop is not.
- HTTP 401 message=ApplicationAuthenticationFailed; errorCode=003200: environment mismatch, sandbox keys against https://quickbooks.api.intuit.com or the reverse. Keys, tokens, base URL, and company type must all belong to one environment.
- 'Please enter a unique valid redirect URI.' in the Intuit portal: usually http://localhost typed into the Production section, which accepts only HTTPS, or a duplicate entry.
- HTTP 403 AuthorizationFailure on /v3/company/ calls: wrong realmId for the tokens. Use the realmId of the company that granted consent.
- Endless 401s after roughly 100 idle days: the refresh token died and Named Credentials cannot recover silently. Re-run Authenticate on the principal.
Security Checklist Before You Ship
- The Client ID and Client Secret live only in your password manager and the Auth Provider. Never in email, Slack, spreadsheets, or Apex source.
- Request only com.intuit.quickbooks.accounting. Skip the payments scope and openid, profile, email unless you have a concrete use for them.
- Restrict the permission set granting External Credential Principal Access to admins and the integration user, so nobody else can fire callouts as your QuickBooks principal.
- Store realmId in custom metadata, never hardcoded in Apex, so a sandbox swap or company change is a config edit, not a deployment.
- Write a reconnect runbook: Intuit's November 2025 policy caps refresh tokens at 5 years, accounting-scope connections made since October 2023 hard-expire in October 2028, and admins get reconnect notices 30 and 7 days out. The fix is re-running Authenticate.
- If you ever click Rotate Secret in Intuit, update the Auth Provider in the same change window. The old secret dies the moment you rotate.
Where This Fits in the Full QuickBooks Build
Credentials are step one of roughly six. The full build layers on object mapping (Account to Customer, Opportunity or Order to Invoice), an Apex or Flow sync layer, error handling for QuickBooks validation faults, and a reconciliation job that catches drift. The entire architecture is in our technical guide at cloudsheer.com/integrations/quickbooks/technical-guide.
If you build with Claude Code, install our free skill encoding this playbook: npx skills add shivamgoel-cloudsheer/Claude-skills --skill quickbooks-salesforce. It scaffolds the Named Credential setup, callout wrappers, and sync patterns we use on client projects.
How Cloudsheer Can Help
Cloudsheer is a Salesforce consultancy that has delivered QuickBooks integrations on 8 client projects, from a one-way invoice push for a small services firm to bidirectional payment sync for a multi-entity distributor. We have hit every error above in a real org, which is why this article quotes exact messages.
If you want the whole integration built, or a second pair of eyes on the one you started, we scope it in a single call: current state, object mapping, edge cases, and a fixed-price plan. Book a free discovery call at cal.com/cloudsheer-consulting/30min.
