Salesforce Integration
The shared server-side plumbing behind Contact, Subscribe, and Unsubscribe: one integration, three forms.
Approach
Published as @brown-forman/p1-salesforce-forms: the Salesforce client, CSRF
handling, and Turnstile verification live in the package; brand sites supply
credentials and drop in the Puck components.
Server side: one Next.js API route per form type:
app/api/csrf-token/route.ts : issues a CSRF token for the session
app/api/submit-contact-us/route.ts : validates + proxies to Salesforce
app/api/subscribe/route.ts : validates + proxies to Salesforce
app/api/unsubscribe/route.ts : validates + proxies to SalesforceEach route: verifies the CSRF token, verifies the Turnstile response server-side (never trust a client-reported pass/fail), validates the payload, then calls Salesforce with org credentials that only ever live in server env vars.
A Salesforce Web-to-Lead POST is fire-and-forget: if it fails, there is no record anywhere that the visitor ever submitted the form. See Form Builder → Submission Storage for the plan to persist every submission before Salesforce (or notification email) is even attempted.
Config shape
interface SalesforceFormsConfig {
salesforce: {
orgId: string; // brand-specific, confirmed, not a shared constant
apiCredentials: { clientId: string; clientSecret: string }; // server-only
};
brandId: string; // e.g. "45" for Herradura, carried on every submission
segmentId?: string;
sourceCode?: string;
turnstileSiteKey: string; // public, client-side
turnstileSecretKey: string; // server-only
contactCategories?: string[]; // powers the Contact form's Category picklist
}Resolved
Confirm the DOB/country-on-every-form requirement: Confirmed real, but Herradura’s own implementation satisfies it via the site-wide gate, not per-form fields. Our build follows that pattern.Is there one shared Salesforce org across all Brown-Forman brands, or one per brand?: Confirmed: segmented per brand.orgIdis brand-specific config; see thebrand_id/segment_idevidence on Subscribe.