A customer messages you on WhatsApp on Monday, DMs your Instagram on Wednesday, and pings your Telegram bot on Friday. Your system sees three different strangers.
This post shows you how to fix that. You'll learn how to design a single customer view across WhatsApp, Instagram, and Telegram: the data model, the identity resolution logic, the merge strategy, and the consent and CRM-sync details that most tutorials skip.
What Is a Unified Customer Profile?
A unified customer profile is one record that represents one real person, no matter how many channels or identifiers they use to reach you. It combines identities, contact details, conversation history, and consent state into a single unified customer view, often called a 360 customer view.
Without it, every channel becomes its own silo:
Support asks the same question the customer already answered on another app.
Marketing sends the same offer three times.
Analytics counts one person as three "users."
AI agents answer without context because they can't see the full history.
The goal of any customer database platform, whether you buy it or build it, is to keep unified customer data in one place and make it the source of truth for every team and every automated agent.
What Is Customer Identity Resolution?
Customer identity resolution is the process of deciding that two or more identifiers (a WhatsApp number, an Instagram-scoped ID, a Telegram user ID) belong to the same person. It is the core of any unified customer database.
How does customer identity resolution work?
There are two approaches:
Deterministic matching: link records on an exact, trustworthy key such as a verified phone number or email. It is precise and auditable.
Probabilistic matching: link records by scoring similarity (name, timing, behavior). It is flexible but produces false positives.
Tip: Start deterministic-only. A wrongly merged profile (two people combined into one) is far more damaging than a missed merge (one person split in two). You can always merge later. Un-merging is painful.
The takeaway: the channel-scoped ID is your primary key per channel, and phone or email are the bridges between channels. Users hand you a bridge only when they choose to, for example by typing an email in chat, sharing a Telegram contact, or filling a form.
Architecture: How to Build an Omnichannel Customer Profile
An omnichannel customer profile sits at the end of a simple pipeline:
Ingestion: webhooks from WhatsApp, Instagram, and Telegram.
Normalization: convert each payload into one common event shape.
Identity resolution: map the event to a customer, creating or merging as needed.
Profile store: the unified customer database.
Sync: push changes to your CRM, analytics, and AI agents.
How to Create a Unified Customer Profile (Step by Step)
Step 1: Separate identities from profiles
The most important design decision: a customer is not an identity. A customer has many identities. Model them as two tables.
CREATE TABLE customers (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
display_name TEXT,
email TEXT,
phone_e164 TEXT,
merged_into UUID REFERENCES customers(id),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE identities (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
customer_id UUID NOT NULL REFERENCES customers(id),
channel TEXT NOT NULL CHECK (channel IN ('whatsapp','instagram','telegram')),
external_id TEXT NOT NULL,
handle TEXT,
verified BOOLEAN NOT NULL DEFAULT false,
first_seen_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (channel, external_id)
);
CREATE INDEX idx_customers_phone ON customers (phone_e164);
CREATE INDEX idx_customers_email ON customers (lower(email));
The UNIQUE (channel, external_id) constraint is your safety net against duplicate identities when two webhooks arrive at the same moment. The merged_into column lets you soft-merge profiles without losing history.
Step 2: Normalize every channel into one event shape
Don't let channel-specific payloads leak into your business logic. Convert them at the edge.
type Channel = "whatsapp" | "instagram" | "telegram";
interface NormalizedMessage {
channel: Channel;
externalId: string;
displayName?: string;
handle?: string;
phone?: string; // E.164, only when the platform gave it to us
email?: string;
text?: string;
receivedAt: Date;
}
export function fromWhatsApp(payload: any): NormalizedMessage[] {
const out: NormalizedMessage[] = [];
for (const entry of payload.entry ?? []) {
for (const change of entry.changes ?? []) {
const value = change.value ?? {};
for (const msg of value.messages ?? []) {
const contact = (value.contacts ?? []).find((c: any) => c.wa_id === msg.from);
out.push({
channel: "whatsapp",
externalId: msg.from,
displayName: contact?.profile?.name,
phone: "+" + msg.from,
text: msg.text?.body,
receivedAt: new Date(Number(msg.timestamp) * 1000),
});
}
}
}
return out;
}
export function fromInstagram(payload: any): NormalizedMessage[] {
return (payload.entry ?? []).flatMap((entry: any) =>
(entry.messaging ?? [])
.filter((evt: any) => evt.message && !evt.message.is_echo)
.map((evt: any) => ({
channel: "instagram" as const,
externalId: evt.sender.id, // IGSID; fetch name/username via the profile API later
text: evt.message.text,
receivedAt: new Date(evt.timestamp),
}))
);
}
export function fromTelegram(update: any): NormalizedMessage[] {
const msg = update.message;
if (!msg?.from) return [];
// A user can share someone else's contact, so only trust it if it's their own.
const ownContact = msg.contact && msg.contact.user_id === msg.from.id;
return [{
channel: "telegram",
externalId: String(msg.from.id),
handle: msg.from.username,
displayName: [msg.from.first_name, msg.from.last_name].filter(Boolean).join(" "),
phone: ownContact ? normalizePhone(msg.contact.phone_number) : undefined,
text: msg.text,
receivedAt: new Date(msg.date * 1000),
}];
}
Note: Webhook payload fields change between API versions. Verify these snippets against the current Meta and Telegram docs before shipping.
Step 3: Resolve identity on every event
This is where you unify customer data across channels. The order of checks matters: exact channel identity first, then deterministic bridges, and a new customer only as a last resort.
// Pseudocode: `tx` is your transactional data layer.
export async function resolveCustomer(tx: Tx, m: NormalizedMessage): Promise<string> {
// 1. Have we seen this exact channel identity before?
const known = await tx.identities.find(m.channel, m.externalId);
if (known) return followMergePointer(tx, known.customerId);
// 2. Do we have a deterministic bridge (verified phone or email)?
const candidates = await tx.customers.findByKeys({ phone: m.phone, email: m.email });
let customerId: string;
if (candidates.length === 1) {
customerId = candidates[0].id;
} else if (candidates.length > 1) {
customerId = await mergeCustomers(tx, candidates.map((c) => c.id));
} else {
// 3. No match: create a new profile.
customerId = await tx.customers.create({ displayName: m.displayName, phone: m.phone });
}
await tx.identities.insert({
customerId,
channel: m.channel,
externalId: m.externalId,
handle: m.handle,
verified: Boolean(m.phone || m.email),
});
return customerId;
}
To identify the same customer across channels, you need a bridge event. Common ones:
The customer shares a phone number or email in chat, and you extract and verify it.
A Telegram user taps a "Share my contact" button.
The customer clicks a deep link from one channel into another (for example, a wa.me link or a Telegram start parameter carrying a signed one-time token).
The customer logs in or fills a form on your site, which ties multiple identities to one account.
Step 4: Merge profiles safely
Merging is the most dangerous operation in the system. Make it transactional, reversible, and logged.
BEGIN;
-- Move every identity and conversation to the surviving profile
UPDATE identities SET customer_id = :survivor WHERE customer_id = :loser;
UPDATE conversations SET customer_id = :survivor WHERE customer_id = :loser;
-- Leave a pointer instead of deleting the old record
UPDATE customers SET merged_into = :survivor, updated_at = now() WHERE id = :loser;
-- Keep an audit trail so the merge can be reviewed or reversed
INSERT INTO merge_log (survivor_id, loser_id, reason, merged_at)
VALUES (:survivor, :loser, 'verified_phone_match', now());
COMMIT;
Simple survivorship rules that work well:
The oldest profile becomes the survivor, which keeps IDs stable for downstream systems.
For conflicting fields, the most recently verified value wins.
Never overwrite a verified value with an unverified one.
Step 5: Track consent per channel
A unified profile does not mean unified permission. Consent to receive WhatsApp messages says nothing about Instagram or Telegram, and each platform has its own messaging rules, including time windows for replying to customers.
CREATE TABLE consents (
customer_id UUID NOT NULL REFERENCES customers(id),
channel TEXT NOT NULL,
purpose TEXT NOT NULL, -- e.g. 'support', 'marketing'
status TEXT NOT NULL CHECK (status IN ('granted','revoked')),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (customer_id, channel, purpose)
);
Warning: When you merge two profiles, merge consent conservatively. If either profile revoked consent for a channel and purpose, treat the merged profile as revoked until you have a fresh opt-in.
How to Connect Social Media Customer Data to a CRM
Once you have a single customer view, the last mile is pushing it to your CRM. Treat your unified database as the source of truth and the CRM as a downstream consumer.
Store your internal customer.id as an external ID on the CRM contact.
Upsert CRM contacts by that ID first, then fall back to email or phone.
Emit events such as customer.created, customer.updated, and customer.merged, and deliver them through an outbox table so nothing is lost.
{
"type": "customer.merged",
"survivor_id": "b2f1c9e0-0000-0000-0000-000000000001",
"merged_ids": ["a7d3f4c1-0000-0000-0000-000000000002"],
"occurred_at": "2026-09-21T09:30:00Z"
}
The customer.merged event matters most. Without it, your CRM will keep two contacts for one person long after your database has fixed the problem.
Why a Unified Customer View Matters for AI Agents
If you're building agentic or multi-agent systems, the profile is more than a reporting convenience. It is the shared memory every agent reads from:
A support agent sees purchase history and open tickets from all channels.
A sales agent knows what the customer asked about on Instagram before continuing on WhatsApp.
A routing agent can pick the right channel based on consent and past responsiveness.
Agents that share one unified customer profile stay consistent. Agents that each keep their own per-channel context contradict each other.
Common Pitfalls
Using usernames as keys. Telegram and Instagram handles change. Use numeric or scoped IDs.
Trusting unverified phone numbers. Only match on values the platform or the user has verified.
Merging on fuzzy name matches. Two people named "Sam Lee" are not the same customer.
Hard-deleting the losing profile. Keep a merge pointer and audit log.
Ignoring privacy law. A unified database concentrates personal data, so plan retention, access control, and deletion requests from day one.
Frequently Asked Questions
How do I create a single customer view across multiple messaging apps?
Give each channel identity its own row, link identities to one customer record, and resolve identities deterministically using verified phone numbers or emails. Merge profiles transactionally and log every merge.
How do I combine WhatsApp, Instagram, and Telegram data?
Normalize each platform's webhook payload into one common event shape, then run every event through the same identity resolution step. This is the core of any approach to how to integrate WhatsApp, Instagram, and Telegram into one system.
How do I track customers across multiple messaging apps if they never share a phone or email?
You can't merge them reliably, and you shouldn't guess. Create bridge moments instead: deep links with signed tokens, login prompts, or a friendly request for an email address.
What are customer data solutions, and do I need one?
Customer data solutions are tools and platforms that collect, unify, and activate customer data. If you have more than a couple of channels and a CRM, you need the capabilities, whether you build them or buy them.
Conclusion
Here are the key takeaways:
A unified customer profile is one record with many identities, not one identity per channel.
Use deterministic identity resolution first, and merge only on verified keys.
Normalize every channel at the edge so your business logic stays channel-agnostic.
Make merges transactional, logged, and reversible.
Track consent per channel, and sync merge events to your CRM.
Treat the unified profile as shared memory for your AI agents.

Top comments (0)