"How did you hear about us?"
Owner: whoever owns the lead form, with the loan officers who ask it.
One question, asked once, answers something no amount of instrumentation can: what moved someone before they ever reached a page we can see.
Two doors
Section titled “Two doors”A system that holds a key (a CRM, an agent platform, a loan officer’s tool,
or a middleware in front of any of them) sends Authorization: Bearer <key>
and may say whose answer it is: by email, by an
external id, or by a browser’s anon_id.
A website form posting from the browser cannot hold a key, so from one of our
own origins it may do one thing only: attach an answer to the browser the
_bid cookie (or anon_id) names, if this collector has already bound that
browser to a person. It may not send traits, identity, external_ids or
lead_id; a form that wants to send an email sends it server-side, with a
key. A browser we have never bound gets 400 no identity, as before. The
preflight is answered for owned origins, so the page can call it directly.
The rule that shapes everything else
Section titled “The rule that shapes everything else”A declared answer never earns credit. It is counted, cross-tabbed and shown. It never becomes a touchpoint and never moves a dollar.
That is deliberate. Ask a hundred borrowers where they heard about you and a large share say “Google” — meaning they typed the brand name into a search box after seeing something else entirely. Letting self-report earn credit would route budget toward whichever channel harvested demand and away from the one that created it.
Which is precisely the distortion the matrix exists to measure. Crediting it would mean measuring an error with an instrument made of the same error.
Getting an answer in
Section titled “Getting an answer in”Two doors, one table. Use whichever you already have.
On the lead, through the conversion payload you already send:
{ "idempotency_key": "...", "identity": { "relcu_contact_id": "..." }, "lead": { "external_id": "...", "attrs": { "declared_source": "saw you on Instagram" } }, "event": "created"}No contract change — attrs is already free-form, so whichever system emits
the lead event can carry it today.
Or directly, with your key, from a form handler server-side, a loan officer’s tool typing what someone said, or a voice agent capturing it in conversation:
curl -X POST https://t.makeabeeline.com/v1/declared \ -H "content-type: application/json" \ -H "authorization: Bearer <your key>" \ -d '{ "identity": { "email": "borrower@example.com" }, "declared_source": "I heard you on a podcast", "captured_via": "voice_agent", "reference": "transcript-4471" }'It answers with what it recorded, so a form can confirm rather than promise:
{ "person_id": "...", "bucket": "radio_or_podcast", "matched": "podcast" }reference is a transcript or form id. Keep it — a surprising bucket is only
believable if someone can go and read what was actually said.
An answer nobody can attach to a person is refused, not parked. It would never reach a report, and a queue of orphaned answers would look like progress. So send an email, an external id, or the cookie of a browser already bound.
What the answers turn into
Section titled “What the answers turn into”Free text becomes one of a dozen buckets, and a bucket is deliberately coarser than a channel group.
“Google” cannot tell paid search from organic from someone typing the brand
name after a billboard. “Instagram” cannot tell a paid placement from a
friend’s story. So an answer resolves to Search or Social media, never
to Paid Search. Matching the tracked side’s precision would fabricate it,
and every cell of the matrix would inherit the fabrication.
| Bucket | Typical answers |
|---|---|
| Search | “Google”, “googled you”, “searched online”, “Bing” |
| Social media | “Instagram”, “IG”, “Facebook”, “TikTok”, “Reddit” |
| Friend or family | “a friend told me”, “my brother”, “word of mouth” |
| Real estate agent | “my realtor”, “my agent recommended you” |
| Radio or podcast | “the radio”, “heard you on a podcast” |
| TV, Something in the post, Email or text, Comparison site | as they sound |
| An ad, unspecified | “saw an ad” — they told us something, just not what |
| Been here before | “used you last time”, “existing customer” |
| They do not remember | “not sure”, “no idea” |
| Something else | nothing in the phrase list matched |
Nothing is guessed. Matching is whole words against an explicit list — no stemming, no closest match. A wrong bucket is worse than Something else, because Something else appears on the screen with the raw text beside it and a wrong bucket does not.
Two traps are handled explicitly, both mortgage-specific: bare “commercial” is a loan type here, not a TV ad, and bare “post” is far more often social than mail.
Reading the matrix
Section titled “Reading the matrix”On the data-quality screen: what people say brought them, versus what we tracked, counted in people.
The diagonal is reassurance. The value is off it:
- Says Social, tracked as Paid Search. The social ad moved them; the search ad harvested them. The search budget is taking credit the social budget earned. Spec 326 calls this quantified brand halo and says the report usually reframes a media budget.
- Says Friend or family, tracked as anything paid. You are paying to re-acquire people word of mouth already sent.
- Anything at all, in the “Never tracked” column. These people told us something and we have no visit to point to. That column is the only evidence the system holds that an off-platform channel is doing anything, and it is the shape of demand every other screen counts as unknown (spec 408).
The newest answer wins when someone has answered twice. They were asked about two different loans; the second is not a correction of the first.
The maintenance loop
Section titled “The maintenance loop”Under the matrix is a list of answers that matched nothing, with the raw text and a count. That list is the to-do list for the phrase list.
Read it monthly. Anything appearing more than a handful of times belongs in the list, and the fix is a code change — deliberately, for now. The list needs to be wrong in public a few times before anyone knows what belongs in it, and a config table would let a bad phrase be added silently at three in the morning.
Asking the question well
Section titled “Asking the question well”Not this system’s job, but the data is only as good as the asking:
- Free text beats a dropdown for this report. A dropdown teaches you what you already thought of, and “Something else” is where the interesting answers go to die.
- Ask once, early, on the form that raises the lead. Asking at closing gets you the last thing they remember, which is always your own website.
- Do not make it required. A required field gets the first option in the list, forever.
About two in five people answer when it is optional, which is enough.