Skip to content

Contact Keys and Customer Identity

You keep GadgetSphere’s customer list — about 85,000 records, most of them walk-ins created at a till in under a minute — and twice a week someone asks why the same person has three customer records, or why a phone number typed at GS-KV-01 did not find the customer the web store already knows. Both questions are about the contact key: the identity record BigLedger keeps behind a customer, and the rules it uses to decide that a phone number, e-mail or ID number belongs to somebody it has already met. This page explains what the key is, exactly when and how BigLedger looks one up, why the match is stricter than people expect, and what you can do about duplicates. Fifteen minutes to read; the procedure at the end takes ten.

Meet GadgetSphere

A customer buys a laptop online from GadgetSphere Online (GSO) and gives the web store 012-345 6789 and Someone@example.com. A month later she walks into GS-KV-03 for a charger; the cashier creates a customer record and types 0123456789. Two customer records now exist, in two companies, for one person — and whether BigLedger knows they are the same person depends entirely on what was typed.

What a contact key is

A contact key (bl_crm_contact_key_hdr) is a small record — phone, e-mail, ID number and type, name — that stands for one person or organisation regardless of how many records mention them. Three kinds of record hang off it:

RecordTableWhat it isHow it points at the key
Customer / supplierbl_fi_mst_entity_hdrThe master-data record documents are raised againstcontact_key_guid on the record, plus a row in bl_crm_contact_key_entity_link
Contactbl_crm_contact_hdrOne observation of the person from one source — a web-store sign-up, a marketplace order, an inbound e-mail, a contact-centre call, a CRM contact you typedkey_guid on the contact (no foreign key), plus bl_crm_contact_hdr_entity_link to the customer
Membership cardbl_crm_membership_hdrThe loyalty membershipcontact_key_guid on the card, plus bl_crm_contact_key_member_link

Read the links as “holds the key of”: a key can be linked to many customers (the same person as a customer of GS, GSO and GSD is one key and three entity rows — the design intent) and a customer can, after a merge, be linked to more than one key. The link rows carry link_type and probability columns; nothing writes them, and they are null on every row in every tenant.

Contacts also carry a source (source_guidbl_crm_contact_source_hdr). The platform seeds exactly one source, AI_LEDGER, in every tenant; it is the source stamped by the Unified Contact Center, inbound e-mail and sales leads. Two contacts in a hundred have a source at all — contacts created by the matching jobs below have none.

The scale is real: 81 of the 90 tenants hold keys, 5.7 million of them across the platform; three customers in four have one.

When BigLedger looks a key up

Two passes run, and they use different rules. Knowing both explains every “why didn’t it match” you will meet.

Pass 1 — the moment a customer or supplier is saved

If the record has no key yet and at least one of phone, ID number or e-mail is filled in, the save itself looks for an existing key:

  • the ID number is compared with the key’s id_no or its ref_2 (the column an older integration used for the identification number);
  • the e-mail with the key’s email;
  • the phone with the key’s phone.

Any one field matching is enough. The comparison is exact: no trimming, no lower-casing, no stripping of +60, spaces or dashes. If several keys match, the oldest wins. If none matches, a new key is created together with a contact that shares its guid, and the record is stamped with it.

For GadgetSphere: 0123456789 at the till and 012-345 6789 on the web store are two different strings, so the cashier’s save creates a second key. someone@example.com and Someone@example.com likewise.

Pass 2 — a few minutes later, for customers only

Saving a customer (not a supplier) also queues a background job. It looks, this time, at contacts rather than keys, by e-mail (compared lower-cased on the stored side) and by phone (exact), and does one of three things:

It findsIt does
No contactKeeps the key pass 1 gave the customer; if the customer somehow has none, tries a second “similar key” lookup and otherwise creates key, contact and both link rows
Exactly one contactUpdates that contact’s key with the customer’s details and links the customer to it
More than one contactMerges: every contact is re-pointed at the oldest contact’s key; the customer, its membership, sales leads, contact-centre links and link rows are re-keyed; the other keys are deleted

Two things about this pass are not what the design suggests, and they matter to your duplicates:

  • The e-mail comparison lower-cases the stored value but binds the typed value as is, so a customer saved with Someone@example.com does not find a contact stored as someone@example.com. Save e-mails in lower case.
  • The “similar key” lookup in the no-contact branch binds its ID-number and phone parameters to the e-mail value, so only its e-mail branch can ever match. In practice that branch finds nothing for a phone-only walk-in.

The job also caps itself at 1,000 candidate contacts and logs an error beyond that, which only matters if a placeholder phone number such as a run of zeros has been typed on thousands of records — a real pattern at retail tills.

Contacts saved directly

A contact created through the CRM API or the Unified Contact Center goes through a third, contact-side pass with the most forgiving rule of the three: e-mail, ID number and ref_2 are compared lower-cased on both sides, phone exactly, and candidates whose ID number differs are dropped. A contact with none of e-mail, phone, ID number or ref_2 is rejected outright (CONTACT_HDR_SHOULD_HAVE_AT_LEAST_ONE_VALUE_SET…). A contact saved without a key gets one created.

What the key is used for today

Be clear about what the key does not do, because the name suggests more than the product does:

  • The POS does not search by key. The cashier’s customer search sends the phone number as a criteria on the customer query — it finds customers whose own phone column equals what was typed. A key that links the web-store customer to the phone does not surface the web-store customer at the till; only a customer record with that exact phone does. The cash bill’s contact_key_guid column is always empty in practice: the action that would fill it is never dispatched.
  • Keys are read through the key API, GET core2/tnt/dm/crm/contact-keys/query?phone=… (or email=, id_no=), exact-match, permission TNT_API_CRM_CONTACT_KEY_READ, and through the membership and contact-centre screens that hang records off a key. There is no key screen in the customer, POS or membership applets.
  • Membership cards are keyed the same way: creating or editing a card queues a member merge job that mirrors pass 2 and stamps bl_crm_membership_hdr.contact_key_guid. This is what lets a member’s points follow the person rather than one customer record.
  • Deleting a customer keeps its key and soft-deletes only the two link rows, so the identity survives for the contacts and memberships still attached to it.
  • The key change queue has two designed event types. PUSH_TO_APPIER feeds a marketing-automation vendor and is raised for one hard-coded tenant only; UPDATE_KEY_LINKS is defined and nothing raises it. Neither affects what you see.

What the aggregates say about the result across the platform: 34% of keys are linked to no customer at all (they belong to contacts and members); 4.5% are linked to two or more customers, which is the multi-company case working as designed and the duplicate case, indistinguishably; about 22,000 phone numbers and 23,600 e-mail addresses each sit on two or more keys — those are the exact-match misses, one per formatting variant.

Before you start

  • You have the customer maintenance permission set that includes Entity Merging (see the Customer Maintenance applet).
  • You can call the key API, or have someone who can: TNT_API_CRM_CONTACT_KEY_READ.
  • Agree one phone format for the group before you touch anything — digits only, no country code, is the least error-prone at a till (0123456789). The match is exact, so the format is the policy.

Step 1: Find out whether the two records share a key

By the end of this step you know whether BigLedger already treats the web-store customer and the walk-in as one person.

Customer Maintenance → Customer listing → open each record; then GET core2/tnt/dm/crm/contact-keys/query?phone=0123456789.

Open the GSO record and the GS-KV-03 record and compare the phone and e-mail exactly as stored. Then query the key API with each variant you find. One key returned for both variants means the records are already linked and you are done. Two keys — one per variant — is the usual answer, and it means every screen that walks the key (membership, contact centre) sees two people.

The most common failure: querying with a normalised number and getting nothing. The API is as exact as the save: query with the string as stored.

Step 2: Make the records match, then let the job merge them

By the end of this step both customer records carry the same key.

Customer Maintenance → open the newer record → Edit → set the phone and e-mail to exactly the values on the older record → Save.

Saving a customer whose key already exists does not re-run pass 1 (it only runs when the record has no key), but it does queue pass 2, which now finds the older contact by e-mail (lower case!) or phone and, finding more than one contact, merges everything onto the oldest key and deletes the newer one. Give the queue a few minutes, then repeat the Step 1 query: one key, both records.

The most common failure: the e-mail was saved with a capital letter and the phone was left in the old format, so pass 2 finds nothing and both keys survive. Lower-case the e-mail, match the phone exactly, save again.

Step 3: Decide whether you also want one customer record

Two customers in two companies with one key is the intended shape — GS and GSO are separate legal entities with separate ledgers, and the person is rightly a customer of each. Merge the customer records only when both are in the same company and one is a plain duplicate: Customer Maintenance → Entity Merging, pick the survivor and the duplicates, and check Entity Merge Processing for the job’s status. Entity Merging folds documents and links onto the survivor; it does not touch keys, which Step 2 has already handled.

What success looks like

GET …/contact-keys/query?phone=0123456789 returns one key. Open the GSO customer and the GS customer: both show the same phone and e-mail, as stored. Create a third test customer in GSD with the same phone and save it: it picks up the same key at save time (pass 1) without waiting for the job. Delete the test customer. Ten minutes.

Common mistakes

MistakeWhat you seeFix
Different phone formats across channels (+6012…, 012-…, 012 …)One person, one key per formatPick one format; store digits only; fix the web-store and marketplace mappings to send it that way
Mixed-case e-mail on a customer recordPass 2 never finds the existing contactSave e-mails in lower case
Placeholder phone numbers at the till (0000000000, 0123456789)Thousands of customers on one key; the merge job stops at 1,000 and logs an errorMake phone optional at the till rather than fake (NOT_MANDATORY_PHONE_NO on the customer applet) and clean the placeholders
Expecting the POS to find the web-store customer through the keySearch by phone finds nothing at the tillThe till searches customer records, not keys. Create the customer in the till’s company with the same phone; the key links the two
Typing the registration number into ID typeThe type column holds a number; the ID number column is empty; nothing matchesID type is a label (NRIC, PASSPORT, BRN); the number goes in ID number
Deleting a duplicate customer that has invoicesThe record cannot go; the key stays either wayUse Entity Merging for records with history
Two customers in two companies “look like duplicates”One key, two entity rowsThat is the designed shape for a group. Merge only within a company

Related documentation