Documentation
Every Salesforce call this demo makes
The demo talks to Salesforce from three places, and which place matters more than the endpoints do: the Auth0 Action is the only writer of consent for anyone with an identity, the app reads it back, and one bounded module covers guests who have no identity yet. Grouped by caller for that reason.
Run a query against the org, right now
These are read-only and fixed in code — there is no box to type SOQL into, by design. Writes on this page are documented, never executed.
Sign in to probe your own records — your Contact, your consent history and your bookings. Those queries are always filtered to the signed-in account.
Getting a token
Both the Auth0 Action and the Express app
Every call below is authenticated the same way: an OAuth 2.0 Client Credentials grant against a Connected App, with a Run-As user supplying the record-level identity.
POST
/services/oauth2/token
grant_type=client_credentials&client_id=…&client_secret=…
Exchanges the Connected App credentials for a bearer token. No user is signed in to Salesforce — the Run-As user provides the identity every subsequent write is attributed to.
Gotcha: The host MUST be the org My Domain URL (…my.salesforce.com), not login.salesforce.com. Client Credentials is rejected at the generic endpoint.
BASE
/services/data/v67.0
instance_url from the token response + /services/data/v{API_VERSION}
All REST calls hang off the instance URL returned with the token, never a hard-coded host.
The Auth0 post-login Action
auth0/terraform/actions/postLogin.js — raw REST via axios
This is the integration brain. It runs inside Auth0 on every login and is the ONLY writer of consent for anyone who has an Auth0 identity. It uses axios rather than jsforce because a jsforce bundle is a poor fit for an Action cold start. Calls are listed in the order one login fires them.
GET
/query
SELECT Id FROM Individual WHERE Contact_Point_Key__c = '…' AND Auth0_User_Id__c = null
Reconciliation (P10): looks for an Individual created by an earlier anonymous booking. Runs BEFORE the upsert below — after it, a second Individual would already exist.
Gotcha: Gated on email_verified. An unverified claim inheriting another party’s history is account takeover.
PATCH
/sobjects/Individual/{id}
{ "Auth0_User_Id__c": "auth0|…" }
Adopts that anonymous Individual by stamping the Auth0 id onto it. One-way: once set, the anonymous path can never match it again. Prior consent rows keep their original ConsentGiverId.
PATCH
/sobjects/Service_Booking__c/{id}
{ "Auth0_User_Id__c": "auth0|…" }
Carries any bookings made as a guest across to the now-known customer, so /bookings can show them.
GET
/query
SELECT Id, Name FROM AuthorizationFormText … ORDER BY CreatedDate DESC LIMIT 1
Reads the current policy version. Salesforce is the source of truth, so adding a text row is the whole policy bump — no Action redeploy and no CURRENT_CONSENT_VERSION secret.
Gotcha: Wrapped in try/catch and fails OPEN — a Salesforce outage must not block an existing user’s login.
PATCH
/sobjects/Individual/Auth0_User_Id__c/{auth0 user id}
{ "LastName": "…" }
Upsert by external ID. This is what makes re-login idempotent — it resolves to the existing record instead of creating a duplicate Individual on every login.
PATCH
/sobjects/Contact/Auth0_User_Id__c/{auth0 user id}
{ "LastName": "…", "Email": "…", "IndividualId": "…" }
The Contact "created on signup", linked to its Individual. Same external-ID upsert, same reason.
GET → POST
/query then /sobjects/ContactPointEmail
{ "ParentId": "…", "EmailAddress": "…", "IsPrimary": true }
ContactPointConsent cannot be written without a contact point, so the email is looked up and created if missing. This is the record per-purpose consent actually binds to.
GET
/query
SELECT Id, Name FROM DataUsePurpose WHERE Name IN ('Marketing','Law 25','Data Use')
Resolves the three seeded purposes to their ids so each decision can be written against one.
GET → POST
/query then /sobjects/AuthorizationFormConsent
{ "ConsentGiverId": "…", "AuthorizationFormTextId": "…", "Status": "Signed", "ConsentCapturedDateTime": "…" }
The policy acceptance itself, bound to one specific text version. Queried first so a re-login inside the same version does not double-write. A NEW row per version — prior rows are never updated.
Gotcha: Status is Seen/Signed/Rejected here. Per-purpose opt-in/out lives on ContactPointConsent, not this object.
POST
/sobjects/ContactPointConsent
{ "ContactPointId": "…", "DataUsePurposeId": "…", "PrivacyConsentStatus": "OptIn|OptOut", "CaptureDate": "…" }
One row per purpose per capture — the actual marketing / Law 25 / data-use decisions.
Gotcha: PrivacyConsentStatus here, Status on AuthorizationFormConsent. Two different fields, easy to confuse.
The Express app
app/lib/salesforce.js — jsforce
The app reads consent back to prove it landed, and writes bookings. It writes NO consent for anyone with an Auth0 identity — that stays exclusively in the Action, because one writer is what makes the audit trail trustworthy.
SOQL
Contact → AuthorizationFormConsent + ContactPointConsent
SELECT Id, IndividualId FROM Contact WHERE Auth0_User_Id__c = :uid
Resolves the signed-in user to their Individual, then fans out to the consent rows. Backs the dashboard and the /consent-data audit ledger.
Gotcha: ContactPointConsent is polymorphic — filter via ContactPointId IN (SELECT Id FROM ContactPointEmail WHERE ParentId = …), because ContactPoint.ParentId is not queryable.
SOQL
AuthorizationForm + AuthorizationFormText
newest vN wins
The live policy version shown on /privacy and used to flag stale consent on /demo.
INSERT
AuthorizationFormText
{ "AuthorizationFormId": "…", "Name": "v{n+1}" }
The "Bump policy version" button. Adding a row IS the policy change — everyone re-consents at their next login with no deploy anywhere.
UPSERT
Service_Booking__c on Booking_Reference__c
{ …13 booking fields, "Consent_Version_At_Booking__c": "v35" }
Writes the booking and stamps the consent version in force at that moment, so two bookings either side of a policy bump carry different versions. Upsert on the reference makes a double-submitted confirm yield one row.
SOQL
Service_Booking__c
WHERE Auth0_User_Id__c = :uid ORDER BY CreatedDate DESC
The /bookings read-back, showing each booking beside the consent version it was made under.
The guest consent writer
app/lib/anon-consent.js — jsforce
The one bounded exception to "only the Action writes consent". An anonymous booking has no Auth0 session, so the Action never runs and the app must capture consent itself. Keyed on a normalised email instead of an Auth0 id, so the two writers can never touch the same Individual.
UPSERT
Individual on Contact_Point_Key__c
{ "Contact_Point_Key__c": "you@example.com", "LastName": "…" }
Creates the Individual immediately, with NO Auth0_User_Id__c. Deferring it to reconciliation would mean a second Individual later and a rewritten ConsentGiverId — what the audit trail forbids.
Gotcha: The email normalisation here must match the Action’s exactly. Diverge and reconciliation silently stops matching, with no error anywhere.
SOQL → INSERT
ContactPointEmail
{ "ParentId": "…", "EmailAddress": "…", "IsPrimary": true }
The contact point the guest’s consent binds to, since there is no identity to bind it to yet.
SOQL → INSERT
AuthorizationFormConsent + ContactPointConsent
Status "Signed", then one ContactPointConsent per purpose
Exactly the shape the Action writes, so an adopted guest’s history is indistinguishable from a registered user’s. Idempotent per (contact point, policy version).
Gotcha: If the policy version cannot be read, the guest booking is BLOCKED — the opposite of the Action’s fail-open. Capturing personal data with no recordable consent is the one thing to prevent.
Each probe spends one call against the org's daily API limit. The write calls above are documented only — nothing on this page mutates Salesforce.