Identity Resolution Guide
Identity Resolution Guide
A comprehensive guide to understanding and using identity resolution when integrating with our contact management platform.
Overview
Identity resolution is the process of matching incoming contact data against your existing contacts. It determines whether to create a new contact or update an existing one based on identifiers or IDs like email addresses, phone numbers, customer IDs, and other unique identifiers or IDs
Why Identity Resolution Matters
When integrating with our platform, you'll encounter situations where:
The same person interacts through multiple channels (email, phone, web, app)
Users provide different identifiers at different times (email first, phone later)
External systems have different IDs for the same person
You need to progressively build a complete customer profile over time
Without proper identity resolution, you end up with:
❌ Duplicate contact records for the same person
❌ Fragmented customer profiles across systems
❌ Incomplete view of customer interactions
❌ Poor user experience (multiple accounts, repeated onboarding)
With identity resolution, you achieve:
✅ Single, unified contact record per person
✅ Progressive enrichment as you learn more about each contact
✅ Accurate customer analytics and reporting
✅ Seamless experience across all touchpoints
Core Concepts
What is a Contact?
A contact represents a unique person profile in your workspace. Each contact:
Has a unique system generated ID i.e. Contact ID
Belongs to a workspace only
Contains attributes (name, address, custom fields)
Has events based on a timeline (opened email, viewed page)
Has associated objects (companies, custom objects)
Is associated with one or more identifiers e.g. email, phone number or external customer ID
What is an Identifier?
An identifier is a key-value pair that uniquely identifies a contact:
{
"key": "emailaddress",
"value": "[email protected]"
}Common identifier types:
emailaddress: Email addresses
phonenumber: Phone numbers
customer_id: Your external system's customer ID
user_id: Application user ID
account_id: Account number
Custom keys specific to your business
Key principle: Each identifier value can only belong to ONE contact at a time. This ensures uniqueness across your contact database.
Resolution vs Aliasing
When sending contact data to our platform, you work with two types of identifiers:
1. Resolution Identifiers
Purpose: Update existing or create a contact
These identifiers participate in your chosen identity resolution strategy and determine which contact record to work with.
{
"identifiers": [
{"key": "customer_id", "value": "CUST_12345"}
]
}2. Alias Identifiers
Purpose: Add supplementary identifiers to an already-found contact
These are added on a best-effort basis after the contact is found. If an alias is already taken by another contact, it's simply skipped (not an error).
{
"aliasIdentifiers": [
{"key": "emailaddress", "value": "[email protected]"},
{"key": "phonenumber", "value": "+12345678900"}
]
}Identity Resolution Strategies
Choose the strategy that matches your data quality and business requirements.
Strategy 1: Strict Resolution
When to use:
✅ You have high-quality data with strong guarantees
✅ You want to catch data inconsistencies immediately
✅ You need strict control over identifier associations
✅ Data errors should be surfaced, not hidden
Behavior: All provided identifiers must match a single contact. If they match different contacts, the operation fails with an error.
Example scenario:
Imagine you're integrating a CRM system where each customer has both an email and phone number that should always be together:
Existing data in Bird:
Contact A: email = [email protected]
Contact B: phone = +11234567890
Your integration sends the following to Bird:
{
"strategy": "strict",
"identifiers": [
{"key": "emailaddress", "value": "[email protected]"},
{"key": "phonenumber", "value": "+11234567890"}
],
"attributes": {
"firstName": "Alice",
"company": "Acme Corp"
}
}Result: ❌ ERROR - identifiers match different contacts
Action needed: Investigate why email and phone are on separate contacts in Bird
Use cases:
Financial systems with strict data integrity requirements
Healthcare systems requiring accurate patient matching
Compliance-sensitive operations
Data quality validation and auditing
Strategy 2: First Alias Resolution
When to use:
✅ You collect identifiers progressively over time
✅ You want flexibility in adding new identifiers
✅ You're okay with identifiers staying separate if they conflict
✅ You prefer successful operations over strict validation
Behavior: Uses the first available identifier to find the contact, then tries to add remaining identifiers. If an identifier is already on another contact, it's skipped (not an error).
Example scenario:
Your e-commerce site collects customer data across multiple sessions:
Session 1: User provides email during newsletter signup
{
"strategy": "first_alias",
"identifiers": [
{"key": "emailaddress", "value": "[email protected]"}
],
"attributes": {
"source": "newsletter_signup",
"interests": ["electronics"]
}
}Bird creates Contact A with email
Session 2: Same user makes purchase, provides phone
{
"strategy": "first_alias",
"identifiers": [
{"key": "emailaddress", "value": "[email protected]"},
{"key": "phonenumber", "value": "+19876543210"}
],
"attributes": {
"lastPurchase": "2025-10-22",
"totalSpent": 299.99
}
}Bird finds Contact A via email, adds phone number
Contact A now has both email and phone
Session 3: User provides loyalty card at physical store
{
"strategy": "first_alias",
"identifiers": [
{"key": "emailaddress", "value": "[email protected]"},
{"key": "loyalty_card", "value": "LOYAL_789456"}
],
"attributes": {
"inStoreVisit": true,
"preferredStore": "Downtown Location"
}
}Bird finds Contact A via email, adds loyalty card
Contact is progressively enriched across touchpoints
Use cases:
Multi-channel customer engagement platforms
Progressive web app user tracking
SaaS applications with gradual user onboarding
Marketing automation platforms
Strategy 3: First Resolution
When to use:
✅ You only need to find the contact, not add identifiers
✅ You're updating attributes without changing identifiers
✅ You want minimal impact on existing identifier associations
✅ Read-mostly operations
Behavior: Uses the first available identifier to find the contact. Additional identifiers in the request are ignored.
Example scenario:
Your analytics system updates contact attributes based on user behavior:
You track user activity and want to update their profile
{
"strategy": "first",
"identifiers": [
{"key": "user_id", "value": "USER_12345"},
{"key": "session_id", "value": "sess_abc123"} // Ignored
],
"attributes": {
"lastActive": "2025-10-22T14:30:00Z",
"pageViews": 47,
"featureUsage": {
"dashboard": 15,
"reports": 8,
"settings": 2
}
}
}Bird finds contact by user_id
Bird Updates attributes only
session_id is NOT added to the contact
Use cases:
Activity tracking and analytics
Attribute-only updates
Event processing systems
Webhook handlers that shouldn't modify identifiers
Alias Identifiers
The Challenge
Imagine this scenario:
You're integrating an external CRM system
Customers provide different identifiers through different touchpoints
Sometimes an email or phone is already associated with another contact
You don't want the entire operation to fail because of one conflict
The Solution: Alias Identifiers
Alias identifiers let you add supplementary identifiers on a best-effort basis, independent of your resolution strategy.
Key benefits:
Best-effort addition: Skipped if already taken, not an error
Explicit reporting: Know exactly which were added vs. skipped
Strategy-independent: Same behavior regardless of resolution strategy
Operation success: Conflicts don't fail the entire request
How It Works
{
"strategy": "first",
"identifiers": [
{"key": "customer_id", "value": "CUST_12345"}
],
"aliasIdentifiers": [
{"key": "emailaddress", "value": "[email protected]"},
{"key": "phonenumber", "value": "+15551234567"},
{"key": "linkedin_profile", "value": "linkedin.com/in/customer"}
],
"attributes": {
"firstName": "Maria",
"lastName": "Garcia"
}
}
What happens:
Bird finds or creates contact using customer_id
For each alias identifier:
✅ If available → Added to the contact
✅ If already on this contact → Idempotent success
⚠️ If on another contact → Skipped, reported in response
Response:
{
"contact": {
"id": "contact-uuid-123",
"workspaceId": "workspace-uuid",
"attributes": {
"firstName": "Maria",
"lastName": "Garcia"
}
},
"created": false,
"addedAliases": [
{"key": "emailaddress", "value": "[email protected]"},
{"key": "linkedin_profile", "value": "linkedin.com/in/customer"}
],
"skippedAliases": [
{"key": "phonenumber", "value": "+15551234567"}
]
}
In this example, the phone number was skipped because it's already associated with a different contact. Your application can then decide whether to
Log it for manual review
Update your source system
Simply accept it (phone genuinely belongs to someone else)
Common Use Cases
Use Case 1: Anonymous to Known User Journey
Scenario: Track anonymous website visitors, then link them to authenticated users.
Challenge: You don't know if the email they provide is already in your system.
Solution:
Step 1: Anonymous visitor browses your site
{
"strategy": "first_alias",
"identifiers": [
{"key": "anonymous_id", "value": "anon_xyz789"}
],
"attributes": {
"source": "organic_search",
"visitedPages": ["/products", "/pricing", "/features"],
"firstSeen": "2025-10-22T10:00:00Z"
}
}Creates a new contact with anonymous ID
Step 2: Visitor signs up with email
{
"strategy": "first_alias",
"identifiers": [
{"key": "anonymous_id", "value": "anon_xyz789"}
],
"aliasIdentifiers": [
{"key": "emailaddress", "value": "[email protected]"}
],
"attributes": {
"signupDate": "2025-10-22T10:15:00Z",
"accountType": "trial"
}
}Bird finds contact by anonymous_id
Tries to add email:
- If email is new → Added successfully
- If email exists → Skipped
Benefits:
No data loss if email already exists
Complete visitor journey preserved
Use Case 2: Multi-Channel Customer Support
Scenario: Customers contact you via phone, email, chat, and social media.
Challenge: Same customer uses different channels, you need unified view.
Solution:
WhatsApp interaction
{
"strategy": "first",
"identifiers": [
{"key": "customer_id", "value": "CUST_98765"}
],
"aliasIdentifiers": [
{"key": "phonenumber", "value": "+15559876543"}
],
"attributes": {
"lastContactChannel": "phone",
"lastContactDate": "2025-10-22T09:00:00Z",
"supportTickets": ["TICKET_001"],
"satisfaction": "satisfied"
}
}
Email sign up follow-up
{
"strategy": "first",
"identifiers": [
{"key": "customer_id", "value": "CUST_98765"}
],
"aliasIdentifiers": [
{"key": "emailaddress", "value": "[email protected]"}
],
"attributes": {
"lastContactChannel": "email",
"lastContactDate": "2025-10-22T14:00:00Z",
"supportTickets": ["TICKET_001", "TICKET_002"]
}
}Live chat session
{
"strategy": "first",
"identifiers": [
{"key": "customer_id", "value": "CUST_98765"}
],
"aliasIdentifiers": [
{"key": "chat_handle", "value": "maria_g"}
],
"attributes": {
"lastContactChannel": "chat",
"lastContactDate": "2025-10-22T16:30:00Z",
"chatTranscript": "transcript_link"
}
}
Benefits:
Single contact record with all channels
Complete interaction history
Support agents see full context
Use Case 3: Third-Party Integration
Scenario: Sync data from external systems (CRM, payment processor, marketing tools).
Challenge: External systems have their own IDs, conflicts can occur.
Solution:
Salesforce integration example:
{
"strategy": "first_alias",
"identifiers": [
{"key": "salesforce_id", "value": "SF_00Q123456"}
],
"aliasIdentifiers": [
{"key": "emailaddress", "value": "[email protected]"},
{"key": "phonenumber", "value": "+15551234567"},
{"key": "company_id", "value": "COMP_789"}
],
"attributes": {
"source": "salesforce",
"accountValue": 50000,
"industry": "Technology",
"employees": 150,
"lastSyncedAt": "2025-10-22T12:00:00Z"
}
}Check the response:
{
"addedAliases": [
{"key": "emailaddress", "value": "[email protected]"},
{"key": "company_id", "value": "COMP_789"}
],
"skippedAliases": [
{"key": "phonenumber", "value": "+15551234567"}
]
}Handle conflicts:
Skipped phone means it's on another contact
Investigate if it's a duplicate that should be merged on your external system and synced again
Or accept it (legitimately different contact)
Log for your integration monitoring
Use Case 4: E-commerce Customer Journey
Scenario: Complete customer lifecycle from browsing to purchase to loyalty.
Solution:
Browse: Visitor adds items to cart
{
"strategy": "first",
"identifiers": [
{"key": "device_id", "value": "device_abc123"}
],
"attributes": {
"cartItems": ["product_1", "product_2"],
"cartValue": 89.99,
"browsingCategory": "electronics"
}
}
Checkout: Provides email
{
"strategy": "first",
"identifiers": [
{"key": "device_id", "value": "device_abc123"}
],
"aliasIdentifiers": [
{"key": "emailaddress", "value": "[email protected]"}
],
"attributes": {
"checkoutStarted": "2025-10-22T10:30:00Z"
}
}
Purchase: Completes order
{
"strategy": "first",
"identifiers": [
{"key": "emailaddress", "value": "[email protected]"}
],
"aliasIdentifiers": [
{"key": "order_id", "value": "ORDER_2025_10_001"}
],
"attributes": {
"orderCompleted": "2025-10-22T10:45:00Z",
"orderValue": 89.99,
"paymentMethod": "credit_card",
"shippingAddress": "123 Main St..."
}
}Loyalty: Joins rewards program
{
"strategy": "first",
"identifiers": [
{"key": "emailaddress", "value": "[email protected]"}
],
"aliasIdentifiers": [
{"key": "loyalty_id", "value": "LOYAL_12345"}
],
"attributes": {
"loyaltyMember": true,
"rewardsPoints": 89,
"memberSince": "2025-10-22"
}
}
Benefits:
Complete customer journey in one profile
Marketing can see browsing → purchase conversion
Customer service sees order and loyalty status
Analytics show full funnel
Use Case 5: Mobile App + Web Platform
Scenario: Users interact with both your web app and mobile app.
Solution:
Mobile app installation
{
"strategy": "first_alias",
"identifiers": [
{"key": "mobile_device_id", "value": "ios_device_xyz"}
],
"attributes": {
"platform": "iOS",
"appVersion": "2.3.1",
"installDate": "2025-10-20",
"pushToken": "push_token_abc..."
}
}
User logs in on mobile
{
"strategy": "first_alias",
"identifiers": [
{"key": "mobile_device_id", "value": "ios_device_xyz"}
],
"aliasIdentifiers": [
{"key": "user_id", "value": "USER_456"},
{"key": "emailaddress", "value": "[email protected]"}
],
"attributes": {
"lastLoginMobile": "2025-10-22T08:00:00Z",
"authenticated": true
}
}User accesses web platform
{
"strategy": "first_alias",
"identifiers": [
{"key": "user_id", "value": "USER_456"}
],
"aliasIdentifiers": [
{"key": "web_session_id", "value": "web_sess_123"}
],
"attributes": {
"lastLoginWeb": "2025-10-22T09:00:00Z",
"browser": "Chrome",
"os": "macOS"
}
}Benefits:
Cross-platform user tracking
Can send push notifications using mobile device ID and push token
Can trigger email using email address
Unified analytics across platforms
Checking for Conflicts
Always check the skippedAliases field in the response to understand which identifiers couldn't be added:
Response with skipped aliases:
{
"contact": {
"id": "contact-abc-123"
},
"addedAliases": [
{"key": "emailaddress", "value": "[email protected]"}
],
"skippedAliases": [
{"key": "phonenumber", "value": "+15551234567"}
]
}
What to do when aliases are skipped:
Option 1: Log for monitoring and trend analysis
Option 2: Create a manual review task for your data quality team
Option 3: Investigate if the contacts should be merged in your external system
Option 4: Accept it if the identifier legitimately belongs to another person
Best Practices
1. Choose the Right Strategy for Your Use Case
Use Case
Recommended Strategy
Why
Financial/Healthcare data
strict
Data integrity is critical
E-commerce customer tracking
first_alias
Progressive identifier collection
Analytics/Event tracking
first
Only updating attributes
Multi-channel engagement
first_alias
Flexible identifier management
High-quality CRM sync
strict
Catch inconsistencies early
2. Prioritize Your Identifiers Wisely
The order of identifiers matters for strategies that check priority (first_alias, first).
Good prioritization:
{
"identifiers": [
{"key": "customer_id", "value": "CUST_123"}, // Most reliable
{"key": "emailaddress", "value": "[email protected]"}, // Very reliable
{"key": "session_id", "value": "sess_xyz"} // Least reliable
]
}Poor prioritization:
{
"identifiers": [
{"key": "session_id", "value": "sess_xyz"}, // Least reliable (BAD!)
{"key": "customer_id", "value": "CUST_123"}, // Most reliable
{"key": "emailaddress", "value": "[email protected]"}
]
}Why it matters: If session_id changes frequently but is checked first, you might create unnecessary duplicate contacts.
3. Use Alias Identifiers for Supplementary Data
Resolution identifiers = Critical for finding the right contact Alias identifiers = Nice to have, but not critical
{
"identifiers": [
// Critical: Must have to identify the contact
{"key": "customer_id", "value": "CUST_123"}
],
"aliasIdentifiers": [
// Supplementary: Try to add, but don't fail if you can't
{"key": "linkedin_profile", "value": "..."},
{"key": "twitter_handle", "value": "..."},
{"key": "secondary_email", "value": "..."}
]
}4. Monitor Skipped Aliases
Track when aliases are being skipped to identify data quality issues:
Warning signs to watch for:
High skip rate (>20% of aliases skipped): May indicate duplicate contacts in your system
Specific identifiers always skipped (e.g., certain emails): Suggests a duplicate contact exists
Skip patterns by source: Some data sources may consistently have conflicts
Actions to take:
Review skipped aliases regularly to identify duplicate contacts
Investigate contacts that frequently have identifier conflicts
Merge duplicate contacts when appropriate in your external system
5. Validate Data Quality
Ensure identifiers are properly formatted before sending:
Email addresses
✅ Valid: [email protected]
❌ Invalid: user@ (missing domain), @example.com (missing local part), user @email.com (contains space
Phone numbers (E.164 format):
✅ Valid: +15551234567
❌ Invalid: 555-1234 (missing country code), 15551234567 (missing + prefix), +1 (555) 123-4567 (contains formatting
General rules:
All identifiers must have non-empty values
Trim leading/trailing whitespace
Normalize case (lowercase for emails)
Use consistent formatting (E.164 for phones)
6. Design for Idempotency
Your integrations should be safe to retry
This is safe to call multiple times
{
"identifiers": [
{"key": "customer_id", "value": "CUST_123"}
],
"aliasIdentifiers": [
{"key": "emailaddress", "value": "[email protected]"}
],
"attributes": {
"lastSyncedAt": "2025-10-22T14:30:00Z",
"syncVersion": "v2"
}
}
If called again:
- Same contact is found via customer_id
- Email already present → Reported in addedAliases (not an error)
- Attributes updated with new values
Result: Safe to retry
Troubleshooting Guide
Problem: "Multiple contacts found" Error
Symptom:
{
"error": "multiple_contacts_found",
"message": "The provided identifiers match multiple contacts"
}Cause: Using strict strategy with identifiers that exist on different contacts
Solution:
Option 1: Switch to first_alias strategy if appropriate for your use case.
Option 2: Use only one identifier to resolve, move others to aliases:
Before (causes error)
{
"strategy": "strict",
"identifiers": [
{"key": "emailaddress", "value": "[email protected]"}, // On Contact A
{"key": "phonenumber", "value": "+15551234567"} // On Contact B
]
}After (works)
{
"strategy": "first_alias",
"identifiers": [
{"key": "emailaddress", "value": "[email protected]"} // Find Contact A
],
"aliasIdentifiers": [
{"key": "phonenumber", "value": "+15551234567"} // Try to add
]
}
Option 3: Investigate and merge duplicate contacts in your external system if they represent the same person.
Problem: Aliases Always Being Skipped
Symptom:
{
"skippedAliases": [
{"key": "emailaddress", "value": "[email protected]"}
]
}This means the email is already associated with a different contact.
Diagnosis steps:
Search for the identifier:
GET /v1/contacts/[email protected]
This shows you which contact currently has that email.
Compare contacts:
Do they represent the same person? → Merge needed
Do they represent different people? → Accept the skip
Check your data source:
Is your source system creating duplicates?
Do you need data deduplication before sending?
Problem: Creating Duplicate Contacts
Symptom: The same person ends up with multiple contact records.
Cause: Not providing consistent identifiers across API calls.
Example of the problem:
Call 1: Uses email only
{
"identifiers": [
{"key": "emailaddress", "value": "[email protected]"}
],
"attributes": {
"firstName": "John"
}
}Result: Creates Contact A
Call 2: Uses phone only (but doesn't include email!)
{
"identifiers": [
{"key": "phonenumber", "value": "+15551234567"}
],
"attributes": {
"lastName": "Doe"
}
}Result: Creates Contact B (duplicate!)
Solution: Always include at least one consistent identifier across all your API calls:
Call 1
{
"identifiers": [
{"key": "user_id", "value": "USER_123"}
],
"aliasIdentifiers": [
{"key": "emailaddress", "value": "[email protected]"}
],
"attributes": {
"firstName": "John"
}
}Call 2: Same user_id ensures same contact is found
{
"identifiers": [
{"key": "user_id", "value": "USER_123"}
],
"aliasIdentifiers": [
{"key": "phonenumber", "value": "+15551234567"}
],
"attributes": {
"lastName": "Doe"
}
}Result: Finds existing Contact A, adds phone number
Problem: Validation Errors
Symptom:
{
"error": "validation_error",
"message": "Invalid identifier format",
"details": {
"key": "emailaddress",
"value": "not-an-email"
}
}Common validation issues:
Email addresses:
❌ "user@" → Missing domain
❌ "@example.com" → Missing local part
❌ "user @email.com" → Space in email
✅ "[email protected]" → Valid
Phone numbers:
❌ "555-1234" → Missing country code
❌ "15551234567" → Missing + prefix
❌ "+1 (555) 123-4567" → Formatting characters (use digits only)
✅ "+15551234567" → Valid (E.164 format)
Solution: Normalize identifiers in your source system before sending:
For phone numbers:
Remove all formatting characters (spaces, dashes, parentheses)
Ensure it starts with + followed by country code
US example: (555) 123-4567 → +15551234567
For email addresses:
Convert to lowercase
Trim leading/trailing whitespace
Example: [email protected] → [email protected]
Problem: Race Conditions with Identifiers
Symptom: The same alias is sometimes added successfully, sometimes skipped (non-deterministic behavior).
Cause: Multiple systems or processes trying to add the same identifier to different contacts simultaneously.
Solution: This is expected behavior when concurrent operations occur:
Accept eventual consistency:
The first operation to complete will claim the identifier
Subsequent operations will see it as skipped
Check skippedAliases in the response to handle appropriately
Review patterns:
If the same identifier is frequently skipped, investigate for duplicate contacts
Consider whether the conflicting contacts should be merged
Quick Reference
Strategy Comparison
Strategy
Priority-based?
Add New Identifiers?
Conflict Behavior
Best For
strict
No
Yes
ERROR
High-quality data, validation
first_alias
Yes
Yes
Skip
Progressive collection
first
Yes
No
N/A
Attribute-only updates
Identifier Priority Guide
High priority (check first):
🥇 Customer ID / User ID from your system
🥈 Email address (if verified)
🥉 Phone number (if verified)
Low priority (check last):
Session IDs
Device IDs
Temporary identifiers
Alias Identifiers Decision Tree
Do you need explicit reporting of success/failure?
├─ YES → Use aliasIdentifiers
└─ NO
└─ Will conflicts occur frequently?
├─ YES → Use aliasIdentifiers
└─ NO → Use regular identifiers with first_alias strategy
Common Patterns
Pattern 1: Anonymous → Known
Resolution: anonymous_id
Aliases: emailaddress (when user signs up)
Strategy: first_alias
Pattern 2: Multi-Channel
Resolution: customer_id
Aliases: channel-specific identifiers
Strategy: first_alias
Pattern 3: External Sync
Resolution: external_system_id
Aliases: emailaddress, phonenumber, etc.
Strategy: first_alias (or strict for high-quality data)
Pattern 4: Analytics Only
Resolution: user_id
Aliases: None
Strategy: first
Last updated
Was this helpful?

