Description
Oddeven Lead Routing connects your WordPress forms to Salesforce and keeps the connection
working through the 2026 and 2027 authentication changes.
Built for the auth Salesforce actually supports. Salesforce restricted new
Connected App creation in Spring ’26 and retires the OAuth username-password flow in
Winter ’27. Oddeven Lead Routing is built on External Client Apps with the client credentials
flow, so a connection you set up today is still a supported configuration afterwards.
The username-password flow is not implemented, and never will be.
A submission is saved before it is sent. Every submission is written to the
database with a pending status before any outbound call, then delivered by a
background worker through Action Scheduler. A Salesforce outage, a slow API or a
validation error cannot break your form or lose the lead. Failed deliveries are
retried with backoff and, if they keep failing, land in a dead letter list you can
inspect and requeue.
Credentials are encrypted at rest. Client secrets and tokens are protected with
libsodium authenticated encryption, keyed from a constant you define in
wp-config.php.
No phoning home. The plugin talks to your Salesforce org and nothing else. No
telemetry, no analytics, no licensing service.
Features
- Map form fields to any Salesforce object over the REST API
- Web-to-Lead and Web-to-Case as alternatives when you cannot use the API
- Per feed conditional logic
- Delivery log with retry, dead letter handling and manual requeue
- Ten granular capabilities, so watching the submission queue does not require the
right to edit Salesforce credentials
Combining fields
A mapping row can send several fields as one value, with fallbacks: {first} {last}
sends the name; {company}||{first} {last}||Individual sends the company when the
form has one, the person’s name when it does not, and a fixed word when it has
neither. That is what Salesforce’s mandatory Company field needs from a form most
people fill in as individuals.
Supported forms
- Contact Form 7
- Formidable Forms
- Custom forms — any form your theme or a developer built, connected with one line of code
Formidable Forms
A Formidable form connects the same way a Contact Form 7 one does: choose Formidable
Forms as the source, enter the form’s ID from its Forms list, and map its fields. Fields
are identified by Formidable’s field ID — the number the form itself posts — with the
field’s label shown beside it, so nobody has to remember that “Last name” is field 7.
A Name field is offered part by part — 82.first, 82.last — and so is an Address
field, because Salesforce wants FirstName and LastName apart; the field itself is
still available as one value. Entries are captured the moment Formidable saves them,
before its own actions run, so a
form whose email action is switched off still delivers. The free plugin is enough;
repeater sections from Formidable Pro are handled as one submission, not one per row.
Start from a live form
Paste the address of a page that shows a form and the plugin reads the form from it,
tells you whether it can deliver from that form plugin, and hands you a mapping CSV
with the form id and every field already filled in — the obvious Lead fields
suggested, the rest left for you. Complete the Salesforce column and import it. No
viewing page source, no typing field ids.
Using Web-to-Lead? Upload the form Salesforce generated under Setup Web-to-Lead as
well, and the CSV comes back with both sides filled in: each Salesforce field —
including custom fields by their 00N… id — paired with the matching form field by
label, the org id set, and hidden values such as the record type carried as fixed
values.
Custom forms
A form the plugin has never heard of can still reach Salesforce. From inside the code
that already handles the submission, after it has decided the submission is good:
do_action( 'lbsf_custom_form', 'quote-request', array( 'first_name' => $first_name, 'email' => $email ) );
Everything after that is identical to a Contact Form 7 lead: saved first, queued,
retried, and visible in Activity. Declare the form and its fields under Forms Custom
forms so the mapping can be built before the form has ever been submitted. Fields that
turn up in real submissions are recorded and offered there too, so a field added in code
and forgotten in configuration shows up as something to click rather than as silence.
It is a hook rather than an address the browser posts to, deliberately: your form keeps
its own nonce, spam handling and redirect, and this plugin adds nothing new that faces
the public internet.
Requirements
- WordPress 6.8 or newer
- PHP 8.0 or newer with the sodium extension
- The ability to add one line to
wp-config.php, which holds the encryption key - A Salesforce org where you can create an External Client App
External services
This plugin connects to Salesforce, and to nothing else. It contacts no service
operated by us, sends no telemetry, and transmits nothing at all until you have
configured a connection and connected a form to it. A visitor loading a page never
causes an outbound request.
Your Salesforce org
The Salesforce org you connect, at the My Domain address you supply, for example
https://yourcompany.my.salesforce.com. It is used to obtain an OAuth access token, to
read the object and field list so you can map fields to it, and to create records from
form submissions.
What is sent, and when:
- Your External Client App’s consumer key and secret, when an access token is requested
— when you connect, when you use “Test connection”, and when a token expires. - The name of the Salesforce object you chose and a field list request, when you open
the mapping editor. - The form field values you have mapped, when a queued submission is dispatched. Only
the fields named in your mapping are sent; nothing else from the submission is.
Salesforce Web-to-Lead and Web-to-Case
Used only if you choose that delivery method for a form, instead of the API.
- https://webto.salesforce.com/servlet/servlet.WebToLead for production orgs
- https://test.salesforce.com/servlet/servlet.WebToLead for sandboxes
Your Salesforce organisation id and the mapped form field values are sent when a
queued submission is dispatched.
Salesforce sign-in
Used only if you choose the browser sign-in method when creating a connection. Your
browser is sent to https://login.salesforce.com, https://test.salesforce.com or your
own My Domain address to authorise the plugin, and the resulting authorization code is
exchanged there for an access token.
All three are provided by Salesforce, Inc. Terms of service:
https://www.salesforce.com/company/legal/agreements/ . Privacy policy:
https://www.salesforce.com/company/legal/privacy/ .
Screenshots






Installation
- Upload the plugin to
/wp-content/plugins/oddeven-lead-routing-for-salesforceand activate it. - Add an encryption key to
wp-config.php. This step is required: until it is done the plugin stores no Salesforce credential at all, and says so on screen. Click Generate a key on that screen: the key is added towp-config.phpfor you when the web server can write that file, and shown once so you can keep a copy somewhere safe. If the file cannot be written, you are shown the line to add yourself — a hosting file manager or SFTP is enough — or run wp oddeven-lead-routing key:generate: define( ‘LBSF_ENCRYPTION_KEY’, ‘base64 of exactly 32 bytes’ ); - In Salesforce, create an External Client App and enable the client credentials flow for it.
- In WordPress, go to Oddeven Lead Routing, add a connection with your consumer key and secret, and test it.
- Create a feed that binds one of your forms to a Salesforce object.
FAQ
-
Why do I need to define LBSF_ENCRYPTION_KEY?
-
It is the key your Salesforce credentials are encrypted with, and it is required.
Until it is defined the plugin stores no credential at all and tells you so.It does not invent a key of its own, and it never borrows one from WordPress. A key
taken from your site’s authentication salts would look convenient right up to the day
something rotated that salt — several security plugins do, on a schedule — at which
point every stored Salesforce credential would become permanently unreadable, with no
way to recover it. Being asked for one line in wp-config.php is the cheaper end of
that trade.Click the Generate a key button the plugin shows while the key is missing. It
adds the line to wp-config.php for you when the web server can write that file, and
shows the key once so you can back it up; it is never kept in the database. If the file
cannot be written — it is read-only, the host needs FTP credentials, or the site sets
DISALLOW_FILE_MODS — you are shown the line to add yourself, with the reason. WP-CLI
users can runwp oddeven-lead-routing key:generateinstead. -
Does this support the username-password OAuth flow?
-
No, deliberately. Salesforce retires it in Winter ’27 and it requires storing a
user’s password. Use the client credentials flow with an External Client App. -
What happens if Salesforce is down when someone submits a form?
-
Nothing visible to the visitor. The submission is already saved, and the delivery is
retried in the background. -
Does the plugin send any data anywhere else?
-
No. There is no telemetry and no phone-home of any kind.
Reviews
There are no reviews for this plugin.
Contributors & Developers
“Oddeven Lead Routing for Salesforce” is open source software. The following people have contributed to this plugin.
ContributorsTranslate “Oddeven Lead Routing for Salesforce” into your language.
Interested in development?
Browse the code, check out the SVN repository, or subscribe to the development log by RSS.
Changelog
1.6.2
A mapping row could be lost on save, and a record type set through Web-to-Lead was ignored.
- The form editor keeps a mapping row whose Salesforce field is not in the cached field list, instead of dropping it silently on the next save. This affected any field added in Salesforce within the last twelve hours, any field typed in a different case through the CSV import, and any field the object description left out: Record Type, Owner, currency, custom lookups. A field written in a different case is corrected to Salesforce’s own; one that really does not exist is refused with a message, as before.
- On a form using Web-to-Lead, a row sending to RecordTypeId now reaches Salesforce as its recordType parameter. It was previously sent under the API name, which Web-to-Lead ignores, so the lead was created on the default record type.
1.6.1
Two hardenings from a whole-plugin security review. Neither was exploitable by a visitor or by any role short of a permission only administrators hold; both are closed so the plugin’s guarantees hold without qualification.
- The Salesforce org id for Web-to-Lead is governed by the connections permission everywhere: a Web-to-Lead feed must name a connection, the org id comes from that connection, and a feed editor without permission to edit connections cannot set or change one. Upgrading copies a feed’s org id onto a connection that had none, so nothing that works today stops; a Web-to-Lead connection now requires an org id and it can be corrected on the connection’s edit screen.
- Changing a connection’s Salesforce address discards its stored secret and sign-in, because a different host is a different org. The client credentials flow asks for the secret again; the signed-in flow needs reconnecting.
- Failure-alert settings are removed on uninstall like every other option.
- A security assurance document for site owners is included under docs/.
1.6.0
Combine fields in one mapping row, with fallbacks.
- A fourth source on the mapping screen, “fields combined”, takes a pattern with field keys in braces and alternatives separated by two bars, tried left to right: {company}||{first} {last}||Individual sends the company when the form has one, the person’s name when it does not, and a fixed word when it has neither. That is what Salesforce’s mandatory Company field needs from a form that mostly hears from individuals. Whitespace is collapsed; context keys work in braces; the importer accepts “template” as a source; and the generated mapping CSV suggests the name pattern for Company when a form has a name but no company field.
1.5.0
Pair a live form with Salesforce’s own Web-to-Lead form, and Web-to-Lead fixes learned from a real cutover.
- Start from a live form takes an optional upload: the HTML Salesforce generates under Setup Web-to-Lead. The plugin reads every parameter and 00N… custom field id with its label, the org id, and the hidden values such as record type and lead source, pairs each with the WordPress form’s fields by label, and produces a Web-to-Lead mapping CSV with the org id filled in. A Salesforce field nothing matches is left blank for you and refused at import until decided. Nobody has to look up a 00N… id again.
- A connection set up for Web-to-Lead can no longer be chosen for Confirmed delivery: the feed editor refuses it and says what to change, the connection list labels such connections, the import preview warns, and the delivery error names both ways out.
- A mapping written with Lead API names (LastName, Website, PostalCode, …) now works over Web-to-Lead: they are translated to last_name, URL, zip and the rest, so switching delivery method does not mean retyping the mapping. A __c API name on a Web-to-Lead form is refused with a pointer to the 00N… id.
- An unconfirmed Web-to-Lead post is labelled “Sent — outcome unknown”, with the submission detail explaining that Web-to-Lead never reports whether the lead was created and naming the address Salesforce emails reports to. The connection’s notification email is described accurately: Salesforce emails a report for every post, successes included, and the lead is still created.
1.4.0
Start a mapping from a live form’s page, and map Formidable Name and Address fields part by part.
- Beside Import mappings on the Forms screen, paste the address of a page that shows a form. The plugin fetches the page once — public addresses only, no redirects followed — reads every form on it, and says for each whether it can deliver from it: Contact Form 7 and Formidable Forms field by field with labels; WPForms, Gravity Forms, Ninja Forms, Fluent Forms, Elementor Pro Forms and Forminator named and marked unsupported. A supported form gets a Download mapping CSV button producing the importer’s format with the form id and every field key filled in, the obvious Lead fields suggested and marked as suggestions, the rest left for you. Fill in the Salesforce column and import. Nothing is written by the analysis.
- Formidable Name and Address fields are now offered part by part — 82.first, 82.last, and line1, city, state, zip, country — so FirstName and LastName can be mapped from a Name field. The field itself is still available as one joined value. Previously the parts were flattened into one list and could not be mapped separately.
1.3.0
Generate a key now adds it to wp-config.php for you.
- Clicking Generate a key now writes the LBSF_ENCRYPTION_KEY line into wp-config.php itself when the web server can write that file — through WordPress’s own filesystem layer, via a temporary file moved into place, read back and checked before success is reported. Most sites are run by someone with a wp-admin login and no hosting access, and “paste this line” was an instruction they could not follow.
- The key is still shown once afterwards so it can be backed up, and is still never kept in the database.
- When the file cannot be written — it is read-only, the host needs FTP credentials, DISALLOW_FILE_MODS is set, the user is not an administrator, or the file has no recognisable place for the line — the key is shown as before, with the reason and what to do instead. An existing unusable definition is replaced rather than duplicated.
1.2.0
Formidable Forms support, and an encryption key you can generate without a shell.
- Formidable Forms submissions can now be delivered to Salesforce. Choose Formidable Forms as the source, enter the form’s ID, and map its fields; each field is identified by its Formidable field ID with the label shown beside it. Entries are captured as Formidable saves them, so a form with no email action still delivers, and repeater rows from Formidable Pro do not produce duplicate submissions.
- While LBSF_ENCRYPTION_KEY is missing or unusable, the plugin’s status region offers a Generate a key button. The key is generated on the spot, shown once with the exact line to add to wp-config.php, and never stored anywhere; reloading shows the button again until the constant is detected. With script enabled a Copy button copies the line and hides it. Requires the settings capability.
- The mapping screen’s field suggestions show a field’s label beside its key where the two differ, and the “this form also collects” note names such fields as Label (key). Nothing changes for Contact Form 7.
1.1.0
Custom forms: deliver to Salesforce from a form this plugin does not know.
- A form built by your theme or a developer can now deliver to Salesforce by firing one action from the handler it already has. It is a hook rather than a public endpoint, so nothing new faces the internet.
- A Custom forms screen, under Forms, is where a hand built form’s fields are declared — which is what lets a mapping be built before the form has ever been submitted. There is no form plugin to ask.
- Field keys that arrive in real submissions are recorded and offered on that screen with an Add button, because a developer adding a field and telling nobody is the normal case rather than an error. An undeclared key can be mapped either way.
- Custom form declarations are included in configuration export and import, and removed on uninstall.
1.0.0
First stable release.
- Salesforce connections through External Client Apps, using the OAuth client credentials flow or a browser sign-in. The username-password flow is not implemented and never will be.
- Contact Form 7 submissions delivered to any Salesforce object through the REST API, or to Web-to-Lead where no Salesforce administrator is available.
- Every submission is saved before it is sent, then delivered in the background by Action Scheduler, so a slow or failing Salesforce cannot break a form or lose a lead. Failed deliveries retry with backoff and end in a dead letter list you can inspect and requeue.
- Field mapping built from the object’s own describe, with validation that refuses a mapping Salesforce would reject on every submission.
- Test Mode records the exact payload that would have been sent without sending it.
- Credentials encrypted at rest with sodium, under a key you supply in wp-config.php.
- Ten capabilities, so the person who watches the queue need not be the person trusted with the client secret.
- Configuration export and import for moving a setup between sites, and WP-CLI commands for everything that does not need a browser.
- Registered with WordPress’s own privacy exporter and eraser, plus a configurable retention sweep.
- No telemetry, no upsells, and no admin notices except for something that is actually wrong.
