As you read on, you might think I’ve contrived a situation with an infinitesimal chance of happening in real life, just for the love of blogging. But this just happened to a user who posted on the Nation!
So here’s the weirdest Marketo Forms factoid you may ever learn: if the 20th field added to a form has an empty name
, you’ll get this generic error on submit:

And inspecting the HTTP 400 response shows:
{
"message" : "checksum invalid",
"error" : true,
"errorCode" : "400"
}
Hold on, a form field can have an empty name
???
Well, no. An <input name>
and <input name="">
are both prohibited by the HTML standard. A native HTML <form>
submit will not send values for those inputs.
But while Marketo forms use standard HTML <input>
elements — a very good thing, I always take care to say! — the data that goes on the wire is assembled using JS. (Most modern forms do this.)
The Forms 2.0 API lets you add a hidden field with an empty name:
MktoForms2.whenReady(function(readyForm){
readyForm.addHiddenFields({
"" : "abc"
});
});
And that field will be sent to Marketo. Note empty query param names are allowed by the URL standard, so this is a perfectly legit x-www-form-urlencoded
payload:
field1=value1&field2=value2&=abc
Again, a native, i.e. JS-less, <form>
can’t create the above payload, but a JS-powered form can and the payload itself is legit, standards-wise. (Whether a given server parses it successfully is another question![2])
Only custom JS can create an empty name
You can’t have fields in Marketo whose Form Field Name is an empty string, so Form Editor will never trip you up like this. And deliberately adding a hidden form field with the name ""
would obviously be bizarre, since it would never store anything in Marketo.
Here’s the way an empty-named field got created without anyone knowingly doing so.
There’s 3rd-party UTM tracking JS on the site. That JS has a section where you’re supposed to map well-known UTM fields to business-specific Marketo Form Field Names:
const queryParamToFieldMap = {
"utm_medium" : "utm_medium__c",
"utm_campaign" : "utm_campaign_latest__c",
"utm_source" : "utm_source_real__c"
};
And then later in the code, that map is referenced to add hidden fields:
MktoForms2.whenReady(function(readyForm){
readyForm.addHiddenFields({
[queryParamToFieldMap.utm_medium] : stored.utm_medium,
[queryParamToFieldMap.utm_campaign] : stored.utm_campaign,
[queryParamToFieldMap.utm_source] : stored.utm_source
});
});
But when the user set up their field map, they didn’t realize they’d literally set one field name to the empty string:
const queryParamToFieldMap = {
"utm_medium" : "utm_medium__c",
"utm_campaign" : "utm_campaign_latest__c",
"utm_source" : ""
};
That is, the code doesn’t set the name to the default when it sees the ""
or anything like that, it just uses ""
as entered.
The dynamic object key [queryParamToFieldMap.utm_source]
is thus the empty string, so they inadvertently did the same as this:
MktoForms2.whenReady(function(readyForm){
readyForm.addHiddenFields({
"utm_medium__c" : "email",
"utm_campaign_latest__c" : "2025-09-25-US-Quad-Complex",
"" : "abc"
});
});
Now we know how you can get such a field on your form. Not so weird really. The weird part happens next.
Field #20 with an empty name is special
You’re about to join a tiny club. Until last week even I didn’t know about this, and because it depends on a “perfect storm” of bad JS + bad luck, doubt anyone else knew.
To be clear, this isn’t a bug in Marketo Forms. As detailed above, your custom JS must have a bug that lets it add an empty-named field. Since such a field could never exist on the Marketo server side, you definitely screwed up first.😛 (Might be nice if addHiddenFields
ignored empty names, but that’d be a belt-and-suspenders thing.)
Here we go.
Marketo form posts automatically include fields named checksum
and checksumFields
as a protective measure.[1] checksum
is a SHA-256 hash of the values of the first 20 fields, joined by |
characters. checksumFields
is a comma-delimited list of the first 20 field names.
On the server side, checksum verification is easy: split checksumFields
to get the names of fields used in the hash; get the values for those names and join with |
; SHA-256 the |
-delimited string; compare with the hash in checksum
.
All good in normal use. But a trailing comma in checksumFields
messes it up. It shouldn’t be possible to have a trailing comma, because it shouldn’t be possible to have an empty 20th field name! But indeed, when you have buggy JS, you can end up with:
checksumFields=f1,f2,f3,f4,f5,f6,f7,f8,f9,fA,fB,fC,fD,fE,fF,fG,fH,fI,fJ,
That gets split into 19 field names (f1
through fJ
), rather than 20. But the checksum
was a SHA-256 of all 20 fields. Therefore the server-side and client-side hashes don’t match: "message" : "checksum invalid"
.
Yep, multiple things have to go wrong at once
There was code that could potentially add the field ""
and the end user did the thing that made ""
get added and that field was the 20th field added to the form.
Not sure what the takeaway should be but this was a nightmare to debug!
Notes
[1] Other libraries use checksums/hashes to ensure data integrity (i.e. to detect bit errors) but Forms 2.0 uses checksums chiefly to discard “dumb” form posts, either from sources like 3rd-party webhooks or malicious bots that can’t do SHA-256. Presumably only the first 20 fields are digested to protect server resources; if you were aiming for data integrity, you’d digest all the fields and send the hash in Content-Digest
.
[2] PHP’s native parse_str
query string parser ignores query params with empty names. JS’s URLSearchParams
parses them correctly. Your favorite package/framework/engine may or may not understand them.