De-creepifying ZoomInfo FormComplete on Marketo forms

SanfordWhiteman
Level 10 - Community Moderator
Level 10 - Community Moderator

ZoomInfo’s FormComplete plugin is... potentially pretty cool. 😀 (I take care not to endorse any products here, though I didn’t say anything bad, which is a good sign!)

 

FormComplete delivers DiscoverOrg enrichment data directly into form fields, so starting from just an email address, you can get most — or even all — of the form fields pre-filled, in B2B at least.

 

(Note: this is separate from (a) pre-filling form data from Marketo for known leads and from (b) the web browser’s ability to autofill from previously stored data.)

 

The problem, as one of my clients put it well, is that it can be kind of creepy. Seeing your personal data evidently loading from a mysterious service, especially if you know you don’t have that data saved in the browser, ain’t so good:

 zoominfo_default

 

(The inverse relationship between creepiness and friction can extend to activity tracking as well. But I digress.)

 

Much better would be having fields that are populated by FormComplete/DiscoverOrg not show up at all. So all fields other than Email are hidden to start, and only those that remain empty after the lookup, if any, are unhidden:

 

zoominfo_custom_realaddr

 

 

 

So I set out to reverse-engineer the ZoomInfo logic (since it’s sparsely documented, to put it kindly) and hook it into the Marketo Forms 2.0 JS API and came up with something pretty good.

 

I’ll leave the ZoomInfo side to you

The setup within ZoomInfo — you have to be at least a trial customer — involves getting a unique formId for their embed code (that ZoomInfo form ID is not related to the Marketo form ID) and telling them which fields you want to return.

 

Their pricing, I believe, is based on how much firmographic/technographic info you want, so in this case we’ll start with the most basic tier: First, Last, Phone, Title, and Company.

 

Start with a regular ol’ Marketo form

No need for anything too fancy in Form Editor. Just sure all the possible ZoomInfo fields are on the form (they won’t be dynamically injected, so they need to be in Form Editor explicitly).

 

The fields may or may not be marked Required, and potentially some could be Hidden. The assumption in the code is that any non-Hidden field that’s set up in ZI, but for which ZI doesn’t have data, will be shown to the end user.

 

Ergo, if Company is a Text field on the form, and ZI doesn’t know the person’s Company, then Company is revealed so they can fill it in.

 

If you want to send ZI’s info to Marketo whether it’s empty or not, and never show it to the end user, then you’d set the field as Hidden in Form Editor, not Text.

 

Take control of field visibility in JS/CSS

A prerequisite for any such project is pinpoint, client-side control over the visibility of fields that are set to always-on, always-visible in Form Editor — that is, not in the Progressive Profiling block, and not of type Hidden.

 

And the way to get that control is the FormsPlus-Tag JS library, available from this link: teknkl-formsplus-tag-0.2.3.js. Download the file to your server — please don’t try to serve it from my CDN, it’s not guaranteed to be cross-domain-linkable — and include it as a remote <script> anywhere after the Forms 2.0 embed code and it’ll make you very happy.

 

FormsPlus-Tag adds a data-wrapper-for attribute to the rows and columns of the form DOM. The value is a space-delimited list of all the fields that are inside the element. (In the example below, it’s a single-column form, but it also works with multi-column forms, where the value would be like data-wrapper-for="Email FirstName LastName", just like a class attribute.)

 

2020-08-04-23_41_00-CodePen---Inference-MktoForms2-__-ZoomInfo-hook-v1.0.1--RESTRICTED-

 

This simple addition to the markup allows you to hide rows and columns using CSS styles. For a simple, admittedly off-topic example: imagine you want to dynamically hide the Company if some other conditions on the page (not just on the form!) are met.

 

You could add a class no-company to the <form> element before you load FormsPlus-Tag, and have this CSS:

.mktoForm {
   visibility: hidden;
   position: absolute;
}
.mktoForm[data-initial-wrapper-tagging-complete="true"] {
   visibility: visible;
   position: static;
}
.mktoForm.no-company > .mktoFormRow[data-wrapper-for~=Company] {
   display: none;
}

 

Note the .mktoForm as a whole is hidden until FormsPlus-Tag has added its special “I’m done tagging” flag, data-initial-wrapper-tagging-complete="true", to the form element. Otherwise, people would see Company for a moment before it disappears. You don’t want that!

 

FormsPlus-Tag also does a lot more than this, but I don’t want to get bogged down today. The important thing is we do have a way to selectively unhide fields. Now over to ZoomInfo.

 

Take control of the ZoomInfo field-filling logic

Turned out there were 2 different things to hack away at on the ZoomInfo side:

  1. Firing a JS callback when ZoomInfo server returns a set of found values.
  2. Stopping ZoomInfo from also filling <input>s with the values, even though it had fired the callback and seemingly passed control over to JS.

I’ll just dip a bit into each area.

 

Figuring out the callback logic was pretty easy: it’s not publicly documented, but it is built-in. So I didn’t need to hack the Ajax call or anything. You can add a callbacks.onMatch function to the global window._zi object, before injecting the ZoomInfo script:

window._zi = { 
  callbacks: {
    onMatch: function(ziFieldsObj){
      // do something with the fields in ziFieldsObj (a JS Object)
    }
  }
};

 

The harder part, surprisingly, was stopping ZI from filling fields. Normally, you assume a callback function would let you return false; or set dontDoYourDefaultThing = true; or something like that to stop the out-of-the-box behavior from happening. But there’s no such function here, it just assumes you want it to fill the fields anyway!

 

A little more peering at their code showed a pretty easy workaround. If you set the attribute data-hasusertyped to "true" on an element, ZoomInfo thinks the person has overridden the ZoomInfo-supplied data with their own. So you can just add that attribute to all fields to start out. That gives you complete control from JS.

 

Putting it together

You’ll need the teknkl-formsplus-tag-0.2.3.js file provided above, and these 2 ZoomInfo-specific files:

 

teknkl-zoominfo-smartreveal-1.0.1.css

teknkl-zoominfo-smartreveal-1.0.1.js

 

As with the FormsPlus-tag file, please download and re-upload to your own server.

 

The CSS is very simple. I wanted a smooth reveal of the empty fields instead of just blasting them into view, so it transition-s the height. You’ll be able to tweak/remove that effect if you know CSS.

 

The JS is quite precise and I don’t recommend modifying it on your own. If something doesn’t make sense or seems to be missing just ask me. It also has a number of advanced, optional behaviors that aren’t likely to be necessary. (I’d thought during development that the UX might be weird in some edge cases. Turned out those cases were covered but I left the advanced code in there anyway.)

 

To deploy the solution, include the 3 remote files in the following order, after a li’l config block:

<script>
window.smartZIRevealUserConfig = {
  ziManagedFields : [
     "Email",
     "FirstName",
     "LastName",
     "Phone",
     "Title",
     "Company"
  ],
  ziLookupField : "Email",
  ziFormId : "s8YcuR7L0CVxStvxZ2wH"
};
</script>
<link id="teknkl-ZoomInfo-CSS-1.0.1" rel="stylesheet" href="https://yourserver.example.com/teknkl-zoominfo-smartreveal-1.0.1.css">
<script id="teknklFormsPlus-Tag-0.2.3" src="https://yourserver.example.com/teknkl-formsplus-tag-0.2.3.js"></script>
<script id="teknklZoomInfo-JS-1.0.1" src="https://yourserver.example.com/teknkl-zoominfo-smartreveal-1.0.1.js"></script>

 

(This above completely replaces the default ZoomInfo-provided embed code.)

 

The config block is simple. ziManagedFields are all the same fields you set up in ZoomInfo’s app. ziLookupField is almost certainly going to be capital-E Email, but hey, there might be some weird exception, so it’s configurable. ziformId is the ZoomInfo-assigned form ID (remember, nothing to do with the Marketo form ID).

 

There’s also a special “developer-only” mode (not my creation — it’s a ZoomInfo feature, but the code lets you enable it easily). Enter dev mode by adding the special hash #dev to your page URL. In this mode, you can use 1 of 3 test addresses:

full-match@zoominfo.com
partial-match@zoominfo.com
no-match@zoominfo.com

 

Each of those addresses sends back a selection of hard-coded fields, so you can see what happens when/some/none of the fields are filled. Dev mode doesn't count any API calls/rate limits against your ZoomInfo subscription, which is great!

9673
14
14 Comments
SanfordWhiteman
Level 10 - Community Moderator

We're trying to figure out how to prevent Zoominfo from pushing data to fields if they are already populated. We dont want Zoominfo to overwrite phone numbers and titles if they are already provided. What would you recommend?

Already populated on the server, or already populated on the form (i.e. via Pre-Fill)?

SarahDuff
Level 1

@SanfordWhiteman 

 

For example, The form is set up to capture title, phone number, company name, country, and state.  If title and phone number are already populated within Marketo, we want to block Zoominfo from updating those fields and only populate the missing fields like state, country, etc.

SanfordWhiteman
Level 10 - Community Moderator

If title and phone number are already populated within Marketo, we want to block Zoominfo from updating those fields and only populate the missing fields like state, country, etc.

To do this, you'd need to have separate proxy fields (like ZoomInfo State, ZoomInfo Country, etc.) on the form, then manage the overwrite/ignore logic using a Smart Campaign.

 

Marketo does let you block updates to a field from form fills. But it can't know how the field was filled in the first place — thus built-in blocking would block both user-supplied data and ZoomInfo-supplied data for the same field. You need to dedicate a field to ZoomInfo to manage it separately.

cgrove
Level 2

It seems that for a two column form that this solution doesnt work quite right since the row is getting the data attribute data-zi-managed applied to it but for example, the first row contains 2 columns with email and company. so the email column inside the row gets hidden right off the bat.

 

cgrove_0-1629417033872.png