Why route around Web-to-Lead
Salesforce is a great system of record. Salesforce Web-to-Lead is a serviceable but rigid way to get web leads into it: a fixed hidden-field format, field IDs instead of readable names, weak spam handling, and no submission dashboard outside Salesforce. When a lead fails a validation rule it can disappear with no trace.
Owning your form HTML and routing through splitforms fixes the form-side problems — you get a Tailwind-styled form you control, an AI spam classifier, email notifications, and a dashboard you can audit — while Salesforce stays the system of record. A small stateless proxy translates the signed webhook into a REST API Lead create.
Step 1 — Create a Connected App and get OAuth credentials
In Salesforce Setup, search App Manager → New Connected App.
- Name it "splitforms webhook". Enable OAuth Settings.
- Set a callback URL (any HTTPS URL for the client-credentials flow), and add the
apiscope ("Manage user data via APIs"). - Enable the Client Credentials Flow and assign a run-as integration user with permission to create Leads.
- Save, then copy the Consumer Key and Consumer Secret. Note your instance URL (e.g.
https://yourorg.my.salesforce.com).
Store the key, secret, and instance URL as environment variables on your proxy host — never in the browser.
Step 2 — Map form fields to Lead fields
Salesforce Leads have standard fields — FirstName, LastName, Email, Company, Phone, LeadSource, Description — plus any custom fields (suffixed __c). LastName and Company are required on the standard Lead object, so make sure your form supplies them (or default Company to something like "[Web]" for B2C).
A Lead create request looks like this:
POST https://yourorg.my.salesforce.com/services/data/v60.0/sobjects/Lead
Authorization: Bearer <access_token>
Content-Type: application/json
{
"FirstName": "Ada",
"LastName": "Lovelace",
"Email": "ada@example.com",
"Company": "Lovelace Industries",
"Phone": "+1-555-0100",
"LeadSource": "Web",
"Description": "Hi — I'd like a 15-min demo.",
"UTM_Source__c": "google",
"UTM_Campaign__c": "spring-2026"
}Step 3 — The splitforms → Salesforce proxy
splitforms delivers a signed webhook to any HTTPS endpoint; the proxy holds the Salesforce credentials, gets a token via the client-credentials flow, and creates the Lead. Here's a Cloudflare Worker version:
export default {
async fetch(req, env) {
if (req.method !== "POST") return new Response("Method Not Allowed", { status: 405 });
// 1. (Recommended) verify the splitforms HMAC signature header here.
const submission = await req.json();
const f = submission.data ?? submission; // form fields
// 2. Get a Salesforce access token (client-credentials flow)
const tokenRes = await fetch(`${env.SF_INSTANCE_URL}/services/oauth2/token`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "client_credentials",
client_id: env.SF_CLIENT_ID,
client_secret: env.SF_CLIENT_SECRET,
}),
});
const { access_token } = await tokenRes.json();
// 3. Create the Lead
const lead = {
FirstName: f.first_name ?? (f.name ?? "").split(" ")[0],
LastName: f.last_name ?? (f.name ?? "Web Lead").split(" ").slice(1).join(" ") || "Web Lead",
Email: f.email,
Company: f.company ?? "[Web]",
Phone: f.phone,
LeadSource: "Web",
Description: f.message ?? f.description,
};
const sfRes = await fetch(
`${env.SF_INSTANCE_URL}/services/data/v60.0/sobjects/Lead`,
{
method: "POST",
headers: {
Authorization: `Bearer ${access_token}`,
"Content-Type": "application/json",
},
body: JSON.stringify(lead),
}
);
if (!sfRes.ok) return new Response(await sfRes.text(), { status: 502 });
return new Response("ok", { status: 200 });
},
};Deploy the Worker, then paste its URL into your form's webhook settings in the splitforms dashboard. To prevent duplicate Leads, enable Salesforce Duplicate Rules or switch the create call to an upsert against an external-ID field. Always verify the webhook signature — see sending form data to a webhook and the docs.
Step 4 — The form
Your public form only ever talks to splitforms with the public access key. Swap YOUR_ACCESS_KEY for the one from your free account.
<form action="https://splitforms.com/api/submit" method="POST">
<input type="hidden" name="access_key" value="YOUR_ACCESS_KEY" />
<input name="name" required placeholder="Full name" />
<input name="email" type="email" required placeholder="Work email" />
<input name="company" placeholder="Company" />
<input name="phone" type="tel" placeholder="Phone" />
<textarea name="message" placeholder="How can we help?"></textarea>
<!-- attribution -->
<input type="hidden" name="utm_source" value="" />
<input type="hidden" name="source_page" value="" />
<!-- honeypot -->
<input type="checkbox" name="botcheck" style="display:none" tabindex="-1" autocomplete="off" />
<button type="submit">Request a demo</button>
</form>What to do next
- Other CRMs: Pipedrive · HubSpot
- Webhook fundamentals: send form data to a webhook
- Add the form to your stack: Next.js · React
- Ready to set it up: get a free access key
FAQ
Why not just use Salesforce Web-to-Lead?
Web-to-Lead works, but it posts to a Salesforce-hosted endpoint with a fixed hidden-field format, no real spam protection beyond optional reCAPTCHA, no dashboard of submissions outside Salesforce, and field names you don't control. If a lead fails validation it can silently vanish. Owning your form HTML and routing through splitforms gives you spam filtering, a submission dashboard you can audit, email notifications, and retry-able delivery — then a small proxy creates the Lead via the REST API. You keep Salesforce as the system of record without its form limitations.
Do I need a paid Salesforce edition for API access?
API access is included on Enterprise, Unlimited, and Developer editions, and available as an add-on on some others. If you can create a Connected App and generate OAuth credentials, you can use this method. The free Developer Edition is perfect for testing the integration before you wire it into production.
Is it safe to call the Salesforce API from the browser?
No — and you shouldn't. The Salesforce access token is a secret and must never live in client-side code. That's exactly why this setup uses a small server-side proxy: the browser posts to splitforms (with only the public access key), splitforms fires a signed webhook to your proxy, and the proxy holds the Salesforce credentials and creates the Lead. The secret never touches the page.
Will duplicate submissions create duplicate Leads?
By default the REST API create endpoint will make a new Lead each time. To avoid duplicates, either enable Salesforce Duplicate Rules on the Lead object, or have your proxy do an upsert against an external ID field (for example a hashed email). The proxy example in this guide shows where to add that check.
Can I map UTM parameters and the source page into Salesforce?
Yes. Add hidden inputs to your form (utm_source, utm_campaign, source_page) and populate them with a one-line script. They arrive in the splitforms webhook payload and your proxy maps them to Lead fields (standard LeadSource, or custom fields you create). That gives your sales team full attribution on every web lead.