GAP School Module 03 — Lead Engine Lesson 3.1

One contact form for every possible visitor intent is like having one checkout lane in a grocery store — people with different needs queue up together, the experience is worse for everyone, and you can't prioritize the person who's ready to buy right now over the person who's just browsing.


The situation

The Anchor build before Era 2 had exactly one form: name, email, phone, message. It sat in the footer of most pages. Every submission landed in the same inbox with no context about what the visitor actually wanted.

A credit application — someone who wants financing approval — arrived in the same format as a general "I have a question about hours." Both got the same delayed response. The sales team had no way to know which was urgent without reading every email. The form's job was to capture intent, and it was failing at that entirely.


What I did

Six intent-matched capture surfaces

Each form is placed where the intent is highest and captures the fields that surface actually needs:

  • Unit inquiry — on every listing page; captures stock number and unit interest pre-filled; routes to sales team.
  • Financing pre-qualification — in the financing section and on listing pages; captures income range, desired term, down payment; routes to finance manager.
  • Credit application — full application form; SSN, employment, addresses, references; routes to finance manager with high-priority flag.
  • Trade-in evaluation — in the trade-in section; captures trade info (year, make, model, condition, mileage); routes to sales team.
  • Consignment intake — captures unit details and owner info; routes to sales manager.
  • General contact — footer fallback; everything else; routes to front desk.

The AJAX submission handler

All forms submit via AJAX to a single WordPress action. The action is registered for both logged-in and non-logged-in users — all visitors are unauthenticated from WordPress's perspective.

AJAX action registration
add_action( 'wp_ajax_[client]_lead_submit', '[client]_handle_lead_submit' ); add_action( 'wp_ajax_nopriv_[client]_lead_submit', '[client]_handle_lead_submit' );
Submission handler — validate, create lead, notify
function [client]_handle_lead_submit() { $validation = [client]_validate_submission( $_POST ); if ( is_wp_error( $validation ) ) { wp_send_json_error( [ 'message' => $validation->get_error_message() ] ); return; } $lead_data = [client]_build_lead_data( $_POST ); $lead_id = [client]_create_lead( $lead_data ); if ( ! $lead_id ) { wp_send_json_error( [ 'message' => 'Submission failed. Please try again.' ] ); return; } [client]_route_lead_notification( $lead_id, $lead_data['source_form'] ); wp_send_json_success( [ 'lead_id' => $lead_id ] ); }

Validation — nonce, honeypot, rate limit

Three layers of validation before any lead data is processed or stored.

Three-layer validation
function [client]_validate_submission( $post_data ): WP_Error|true { // Layer 1: nonce check if ( ! wp_verify_nonce( $post_data['_wpnonce'] ?? '', '[client]_lead_submit' ) ) { return new WP_Error( 'invalid_nonce', 'Security check failed.' ); } // Layer 2: honeypot — bots fill hidden fields, humans don't see them if ( ! empty( $post_data['website'] ) ) { return new WP_Error( 'bot_detected', 'Submission rejected.' ); } // Layer 3: rate limit — 3 submissions per IP per hour $ip_hash = md5( $_SERVER['REMOTE_ADDR'] ); $cache_key = '[client]_rl_' . $ip_hash; $count = (int) get_transient( $cache_key ); if ( $count >= 3 ) { return new WP_Error( 'rate_limited', 'Too many submissions. Please wait before trying again.' ); } set_transient( $cache_key, $count + 1, HOUR_IN_SECONDS ); return true; }

Notification routing by form type

Routing array — each form type maps to a recipient and subject prefix
function [client]_route_lead_notification( int $lead_id, string $source_form ) { $routing = [ 'credit_application' => [ 'to' => FINANCE_MANAGER_EMAIL, 'subject' => '[HOT] Credit Application', 'urgent' => true, ], 'financing' => [ 'to' => FINANCE_MANAGER_EMAIL, 'subject' => 'Financing Inquiry', 'urgent' => false, ], 'unit_inquiry' => [ 'to' => SALES_EMAIL, 'subject' => 'Unit Inquiry', 'urgent' => false, ], 'trade_in' => [ 'to' => SALES_EMAIL, 'subject' => 'Trade-In Evaluation Request', 'urgent' => false, ], 'consignment' => [ 'to' => SALES_MANAGER_EMAIL, 'subject' => 'Consignment Inquiry', 'urgent' => false, ], 'general' => [ 'to' => FRONT_DESK_EMAIL, 'subject' => 'General Inquiry', 'urgent' => false, ], ]; $config = $routing[ $source_form ] ?? $routing['general']; [client]_send_lead_notification( $lead_id, $config ); }

Why it matters

Intent-matched forms do two things simultaneously: they make the experience better for the visitor (fewer irrelevant fields, the form asks exactly what makes sense given where they are on the site), and they make the sales team's job easier (credit applications are flagged as hot; general inquiries are not).

The sales team was previously triaging every email manually to decide what was urgent. Routing by form type does that triage automatically. Credit applications are flagged [HOT] in the subject line. The sales team has a clear visual signal the moment the email arrives.


The Anchor build

Six capture surfaces replaced the single footer form. Within the first month, the distribution of submissions by form type revealed something useful: 35% of submissions were unit inquiries from listing pages, 28% were financing pre-qualifications, and 12% were credit applications. The single general form had been capturing all of this with no differentiation. The finance manager was reading through general inquiries to find financing requests manually.

After routing, the finance manager saw only financing and credit application submissions in their inbox. Response time to hot leads (credit applications) dropped from hours to under 30 minutes in the first week — not because anything technically changed, but because the routing eliminated the triage step entirely.


Do this, not that

  • One form per intent, placed at the point of highest intent. A unit inquiry form on the listing page. A financing form on the financing page. A general form in the footer as a fallback only.
  • Register both wp_ajax_ and wp_ajax_nopriv_. Without the nopriv variant, unauthenticated visitors can't submit — which is all of them.
  • Always include a honeypot field. It costs nothing and catches the majority of bot submissions. The field must be hidden from humans (CSS display:none or off-screen positioning) but left visible in the HTML so bots find it and fill it.
  • Rate-limit by IP with a transient. Three submissions per hour is generous for a human, prohibitive for a bot. The transient is cheap; the protection is real.
  • Define your routing array once, in one place. Notification recipients change. When the finance manager changes, you edit one array entry, not six different files.
When you’re ready to build

The lessons are yours. When you want it built, we’re here.

Every lesson stays free — no account, no paywall, no email gate, ever. But if you’d rather have this system standing on your business than wire all 48 lessons yourself, leave your email. We’ll send you a direct line to a build — and you’ll be first to hear when we add new tools to the curriculum.

None of this gates a single lesson. The curriculum was free before you got here and it stays that way.

We’ll use your email to send you a fast-track to a GAP build and occasional notes on how GAP builds digital sales departments. Lessons stay 100% free — no email required to read any of them. We never share or sell your information. Unsubscribe any time. Privacy policy at gapindustriesllc.com/privacy.html.

Done learning how it’s built? We’ll build it.

You came here to understand the system, and now you do. If you’d rather have it standing on your business than spend the next three months wiring it yourself, GAP Concierge is the same architecture from these lessons — a white-label AI agent that knows your catalog and captures your leads — set up for you, from $97/mo.

See GAP Concierge →