Salesforce Integration Troubleshooting
Section 7 of the Salesforce integration guide. Start at the overview.
7. Gotchas
Section titled “7. Gotchas”| Symptom | Cause |
|---|---|
invalid_grant on JWT exchange |
User not pre-authorised on the app, cert/key mismatch, aud pointing at login.salesforce.com for a sandbox, or clock skew |
Username-Password Flow Disabled |
Something still on the retired flow — see 2. Authentication |
No such column 'Secure_Privacy_Subject_Id__c' |
Field-level security, not a missing field. Check that first, then check the API name matches exactly |
INVALID_SESSION_ID repeating |
Session refresh not wired up |
REQUEST_LIMIT_EXCEEDED |
Org daily allocation exhausted, often by a backfill. Check Setup → System Overview |
HTML where JSON was expected, on a 200 |
Missing Accept: application/json. The service content-negotiates and will happily serve you a debug page |
Redirect to /api/login |
Request was not authenticated. This endpoint can redirect rather than return 401, so set redirect: 'manual' |
200 with a ResponseStatus body and no PagedResults |
An API error reported inside a success status — e.g. ApiKey does not exist. Check the envelope, not just the status |
| Consents counted as written but missing from Salesforce | Batch upsert results not inspected. A rejected row returns quietly unless you check success on each result |
| Salesforce datetime fields full of nonsense | Created written through unparsed. It is /Date(1787663774128)/, not ISO 8601 |
| Some consents silently dropped | Code validating the format of ClientId. It is opaque — store what you are given |
| Whole consent history duplicates after an org move | Consent key built from IndividualId, which changes between orgs — see 6. Writing consent |
| One person appears under two identities | CustomUserId written with inconsistent casing. Normalise the email on both the linking call and the match |
| Everything resolves to anonymous | The site is not calling the linking endpoint. Measure coverage before assuming the sync is broken |
403 from /api/consents |
DomainId does not belong to the authenticated account |
429 from Secure Privacy |
40/second, 1200/minute per domain. Wait for the period in Retry-After |
| Consent records dated today | CaptureDate set to new Date() instead of the record’s Created |
| Category mapping stops matching some visitors | Mapped on the display name. It follows the visitor’s locale and is editable, so one CategoryId arrives under several names |
| Acceptances sync, withdrawals do not | Extract filtered by Status, or dates compared against Created rather than LastUpdated |
| Records read twice or missed part-way through a run | No fixed date range. Set both FromDate and ToDate |
| A failed record never retried | Watermark advanced despite failures in the pass. Hold it back — re-reading is safe |
