In short

A thank-you page proves only that a form can say thank you. Leads were lost to a phone field typed as a number and a consent check expecting the wrong shape, and nearly to a validation rule. Test with real data, count both ends, probe before switches.

There is a particular kind of unease that comes from a system that looks fine. The forms are submitting. The log fills up every day. The thank-you page appears on cue. Nothing is red, nothing has errored, and yet the phone is not ringing the way the traffic says it should. You cannot point at anything, so you tell yourself it is a slow month. Knowing how to test a web form end to end is what separates a slow month from a broken form.

That was me, more than once, on a multi-location service business I build and run the web platform for. It has several sites, all feeding enquiries into the same office. Across a few months in 2026 I found three separate ways that platform was losing or damaging real leads. None of them produced an error. Two of them had been running for weeks before anyone noticed. The third I only caught because I checked one more time before pressing a button.

Each one taught me the same thing from a different angle, so I will take them in order.

The phone field was already required, and that was the clue

It started with a reasonable request. The owner asked me to make the phone number required on the enquiry forms, because too many leads were reaching the office without a number anyone could call.

I went to change it and found it was already required. It had been required on every site all along, and people were filling it in.

That is the moment the problem changed shape. If the field is required and visitors complete it, then the number exists when they press Send. Somewhere between their keyboard and the office, it was being damaged. So I stopped looking at the form settings and started looking at what actually arrived.

Every real lead had a phone number that was one digit short.

A number field deleted the leading zero from every real mobile number

Mobile numbers in many countries start with a 0 (the trunk prefix) when people write them the local way. Every customer this business serves writes theirs like that. The popup forms on some of the sites used the form builder’s “number” field type for the phone. A number field treats what you type as a quantity, and a quantity has no leading zero. So a number typed as 0612345678 was stored as 612345678. The visitor typed it correctly. The form accepted it. The number that reached the office was not one you could dial.

The shape of it, rewritten here for illustration. First the field as it was, then the field as it is now:

<input type="number" name="phone" required>

<input type="tel" name="phone" inputmode="tel"
       pattern="0[0-9]{9,10}" placeholder="0XXXXXXXXXX" required>

The pattern above is deliberately generic. Set it to the exact format your own country uses, so the field accepts real numbers and nothing else.

The number field damaged every genuine submission from those popups. Not some of them. Every one, because every real customer number started with the digit the field threw away.

The honest admission is that nobody, me included, looked closely at the numbers themselves. People saw that a phone was there and moved on. A field that is present and wrong looks almost identical to a field that is present and right, until someone tries to ring it.

Spam made the broken form look healthy

The part that kept this hidden for weeks was not the bug. It was the bot.

One of the forms was being hit by a spam bot that sent fake phone numbers. Its numbers did not start with a zero, so the number field did them no harm. They arrived intact, well formed and plausible at a glance. Worse, each junk submission opened a real deal in the CRM, so somebody had to deal with it.

Put those two things together and you get a log that tells a comforting lie. Submissions were steady. Phone numbers were present. Some of them even looked perfectly normal. The traffic that looked the healthiest was fake, and the traffic that looked a bit off was the real customers.

If you only glance at a log to see whether things are arriving, a busy log reassures you. It should not. Volume is not evidence that the right things are arriving in the right shape.

One text field and one format also shut the bot out

The fix had two halves. The first was to replace the number field with a text field on every form, on every site, and give it one enforced mobile format, with a placeholder that shows the visitor exactly what is expected.

The second half matters more than the first. A pattern in the browser is a hint, not a guarantee. People paste numbers with spaces, dashes and a “+” country code, and some requests never go through a browser at all. So the server normalises the number before it validates it. It keeps the digits, turns a “+” country code back into the local leading zero, and then accepts one format or nothing:

function normalise_mobile(string $raw, string $countryCode): ?string
{
    $digits = preg_replace('/\D+/', '', $raw);
    $digits = preg_replace('/^00/', '', $digits);

    if (str_starts_with($digits, $countryCode)) {
        $digits = '0' . substr($digits, strlen($countryCode));
    }

    return preg_match('/^0\d{9,10}$/', $digits) ? $digits : null;
}

Normalise first, then validate. If you validate first, you reject real people for typing a space. If you never validate, you pass damaged numbers downstream, where they break the automations that send booking reminders and confirmations. A malformed number had already done exactly that on one lead, which is part of why this became the top priority rather than a tidy-up.

There was a pleasant side effect. The bot’s fake numbers did not fit the rule. The same change that repaired every real lead quietly stopped the spam, with no extra filter.

While I was in every form anyway, I moved them all onto one lead endpoint. Every site now sends enquiries to the same place, in the same shape, with the same phone rule. Before that, a fix on one site had to be remembered and repeated on the others. Now there is one path to check, not one per site.

A calculator can thank every visitor and deliver nothing

The second episode was quieter still.

The platform has several calculators that help a customer work out whether a job is worth doing and roughly what it would involve. Each one ends with a way to get in touch. When I moved these calculators onto the shared lead endpoint, I did what I now do before any switch: I compared what each one had actually delivered.

One log had entries. The quote calculator’s log was empty.

Not low. Empty. The quote calculator worked for visitors. It did its maths, showed its result, took their details and responded as if all was well. Behind that, the server checked for consent before accepting a quote request, and the check expected the consent value in a different shape from the one the form sent. The form sent a simple yes. The server looked for a field inside an object. The check failed every time, and it failed before anything was written down.

Simplified, the form sent the first line below, and the check looked for the second:

{ "consent": true }

if (empty($payload['consent']['accepted'])) {
    return reject();
}

The field names are illustrative, but the mechanism is the real one. Every quote request was rejected, and nothing recorded the rejection. Nothing was logged and nothing was stored. As far as I can tell, the quote calculator had most likely never delivered a single lead.

I fixed the check so it accepts the value the form actually sends. The broader point is about what a rejection sounds like. A lead that fails validation should be the loudest thing in the system, not the quietest.

It is uncomfortable to write that down, because it was a tool on a platform I run. Nothing about it looked broken, to a visitor or to anyone watching the site. It only showed itself when someone followed a quote request all the way to where the office would see it, and found nothing there.

The switch that would have lost 58% looked safe on paper

The third episode is the one that did not happen, and I think it is the most useful of the three.

The last calculator to move was the savings calculator. Its switch was held back on purpose: the team that runs the CRM needed to move some automation across first, so the plan was to wait for their go-ahead and then switch.

When the go-ahead came, the switch looked routine. I had done several of these already. The endpoint was live and other forms were already posting to it.

Before switching, I probed it anyway. I sent test submissions of each kind the savings calculator produces, with realistic data, and checked what the receiving side accepted.

The savings calculator offers two ways out. A visitor can ask for a quote, or they can ask to have the result emailed to them. The “email me the result” submissions come with an email address and not much else. The new destination’s validation rule rejected those.

Those submissions made up 58% of what the savings calculator sent. Had I switched as planned, more than half of its leads would have vanished. The visitors would still have seen their confirmation. The log on the new side would have shown a healthy stream of quote requests. Nothing would have errored, because the rule was doing exactly what it was written to do.

I held the switch, reported what the probe had found, and waited until the receiving side was changed to accept email-only submissions. Only then did the savings calculator move. As with every one of these switches, I saved the old destination first, so reversing it would take one step rather than a search.

Field note

Re-probing before a gated switch is what stopped a silent 58% loss

That is the line from my own notes, word for word. The switch had already been approved, and the endpoint had already been probed once. The second probe is the point. An approval tells you someone thinks it is ready. A probe on the morning of the switch tells you whether it is, today, for every kind of submission you are about to send.

Count what arrives against what was sent

Put the three episodes side by side and they share one feature. At no point did anything fail in a way that a person would see. The visitor got their thank-you every time. The logs were busy or, in one case, simply empty in a way nobody was checking. Each fault was only visible when someone compared two things that were supposed to match.

That comparison is the habit I now build in. Concretely, it looks like this:

  • Test the whole path with real-looking data. A mobile number written the way your customers write it, leading zero included, a real-looking email, a genuine question. Test data that happens to avoid the bug will pass, as the spam bot’s numbers did.
  • Follow the lead to where a person uses it. Not the thank-you page. Not the log. The record in the CRM, opened by the person who will ring the number, with the number checked digit by digit.
  • Count both ends. For a given week, how many submissions did each form accept, and how many leads arrived from it? If those numbers differ, something is dropping leads. If one of them is zero, find out why today.
  • Probe before every switch. Send each kind of submission a form can produce to the new destination before any real ones go there. Keep the old destination written down, so reversing the change is one step.
  • Make rejection loud. A lead turned away by validation should leave a record someone will see. Silence should mean nothing was submitted, not that everything was refused.

None of this is sophisticated. It is mostly the discipline of not believing the first reassuring thing a system tells you.

You can test your own forms this week with a phone and ten minutes

You do not need to be technical for the most useful part of this.

Pick every form on your site that can produce a lead: the contact form, any popup, any quote request, and any calculator or quiz that asks for details at the end. For each one, fill it in yourself from your phone, the way a customer would, with your own real mobile number written the normal way. If a form offers two ways out, such as “request a quote” and “email me the result”, try both. Then go to wherever your team actually handles leads, find each submission, and read it back carefully. Is every field there? Is the phone number complete, with its leading zero, and does it ring your phone when you dial it from the record?

Next, ask whoever runs your website for two numbers from last month: how many submissions each form received, and how many leads the business logged from each. You are not looking for precision. You are looking for a form where the two numbers are wildly different, or where one of them is zero and nobody has an explanation.

Finally, if anyone is planning to change where your forms send their data, such as a new CRM, a new automation or a new inbox, ask one question before it happens: “Will you send a test of every kind of submission to the new place, and check it arrived, before the real ones go there?” If the answer is anything other than a clear yes, ask them to do it anyway.

Pro tip

Ask for the “before” setting to be written down ahead of any change to where a form sends. If a switch goes wrong, the fastest fix is putting the old value back, and that is only fast if someone saved it.

A quiet log is a question, not an answer

Where things stand now: every form on every site sends to one lead endpoint, with one phone rule, and the old relay is out of the lead path. The phone numbers arrive dialable. The quote calculator delivers. The savings calculator switched only after the receiving side was ready for its email-only leads.

What stays with me is how ordinary all three faults were. A field type. A consent value in the wrong shape. A validation rule written for one kind of lead and meeting another. None of them would have made it into a headline, and together they could have cost the business a meaningful share of its enquiries without a single alert.

A form that says thank you has proved only that it can say thank you. A busy log has proved that something is arriving, not that the right things are. The only proof that leads are reaching your business is a real-looking lead, followed all the way to the person who picks up the phone, and counted against what was sent.