#WhatsApp BSUID Migration: June 2026 Developer Guide
The Breaking Change Coming in June 2026
Picture this: It's July 2026. You wake up to a flood of error messages. Your WhatsApp automation — the one that's been running perfectly for 18 months — is throwing 400 errors on every webhook. Customer messages are coming in, but your system can't match them to user records. Your database foreign keys are broken. Your conversation threading is completely lost.
What happened? WhatsApp switched from phone numbers to Business-Scoped User IDs (BSUID), and your code is still looking for [email protected] in the from field. But now it says bsuid:ABC123DEF456GHI789.
This isn't hypothetical. Meta announced in late 2025 that phone numbers as permanent user identifiers are being phased out. The deadline is June 2026. If your WhatsApp automation uses phone numbers as primary keys, database identifiers, or conversation anchors, you have less than 4 months to migrate.
Here's everything you need to know about BSUID, usernames, and how to update your code without breaking production.
What's Changing: Phone Numbers → BSUID
For as long as WhatsApp has existed, phone numbers were the universal identifier. Every contact, group, and message was tied to a phone number. If you wanted to message someone, you needed their number in the format {country_code}{number}@c.us.
That's changing. Here's what the new system looks like:
Old System: Phone Numbers Everywhere
User identifier format: [email protected]
Characteristics:
- Tied permanently to a SIM card
- Same ID across all businesses (no privacy isolation)
- Portable when you switch businesses (same number = same identity)
- Exposed in every API response and webhook payload
Example webhook payload (old):
{
"event": "message",
"from": "[email protected]",
"to": "[email protected]",
"message": {
"text": "When will my order ship?"
}
}You'd store [email protected] in your database as the user ID, and every future interaction referenced that same phone number.
New System: Business-Scoped User ID (BSUID)
User identifier format: bsuid:ABC123DEF456GHI789 (opaque token, not human-readable)
Characteristics:
- Unique per business (different BSUID for each company the user contacts)
- Not portable across businesses (privacy protection)
- Can be reset by user if they want to "start fresh" with a business
- Phone number still exists but is now optional metadata
Example webhook payload (new):
{
"event": "message",
"from": "bsuid:ABC123DEF456GHI789",
"phone": "[email protected]",
"to": "[email protected]",
"message": {
"text": "When will my order ship?"
}
}Notice the difference: from is now a BSUID. The phone number still appears in a separate phone field for backward compatibility, but it's no longer the primary identifier.
Why the Change?
Three reasons:
1. GDPR and privacy compliance
Phone numbers are personally identifiable information (PII). Under GDPR and similar regulations, businesses need explicit consent to process phone numbers. By switching to opaque BSUIDs, Meta reduces the privacy risk. Users can interact with businesses without exposing their actual phone number in API logs and database records.
2. Reduced phone number exposure
When you share your phone number with one business, that business could technically use it to contact you via other channels (SMS, calls, other apps). BSUID isolates this: each business gets a unique ID that's useless outside the WhatsApp context.
3. Enable username adoption
Usernames (like @alice) are coming to WhatsApp in 2026 — similar to Telegram and Instagram. For usernames to work, WhatsApp needs a stable internal identifier that doesn't change even if a user switches phone numbers. BSUID is that identifier.
Comparison Table
| Aspect | Old (Phone Number) | New (BSUID) |
|---|---|---|
| Format | [email protected] | bsuid:ABC123... |
| Persistence | Forever (tied to SIM) | Per business (changes if user resets privacy) |
| Cross-business | Same ID everywhere | Different ID per business |
| Privacy | Exposes actual phone number | Opaque token |
| Username support | No | Yes (optional) |
| Backward compatibility | N/A | Phone number still available in phone field |
How Usernames Work
In addition to BSUID, WhatsApp is rolling out optional usernames. Think of it like Telegram: instead of sharing your phone number, you can give people your username (@alice), and they can message you directly.
Key points:
- Opt-in for users — Not everyone will have a username. It's a privacy feature, not a requirement.
- Format:
@username(alphanumeric, no spaces, 5-30 characters) - Discovery: Users can search for usernames in WhatsApp to start conversations
- Messaging: You can send messages to
@usernameinstead of a phone number
How to Contact Someone via Username
If a user has set a username, you can message them directly:
Old approach: Phone number required
curl -X POST https://apiv2.waiflow.app/api/v2/messages \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"session_name": "my-session",
"chatId": "[email protected]",
"text": "Hello!"
}'New approach: Username (if available)
curl -X POST https://apiv2.waiflow.app/api/v2/messages \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"session_name": "my-session",
"chatId": "@alice",
"text": "Hello!"
}'Response:
{
"success": true,
"messageId": "3EB0F2A4B5C6D7E8F9",
"timestamp": "2026-02-17T10:30:00Z"
}If the username doesn't exist or the user hasn't set one, you'll get a 404 error. Your code should fall back to the phone number.
Fallback Logic: Try Username First, Phone Number Second
Best practice for maximum compatibility:
async function sendMessage(contact, message) {
// Try username first if available
if (contact.username) {
try {
return await sendViaUsername(contact.username, message);
} catch (err) {
if (err.status === 404) {
// Username not found, fall back to phone
return await sendViaPhone(contact.phone, message);
}
throw err;
}
}
// No username, use phone number
return await sendViaPhone(contact.phone, message);
}This ensures you can message users whether they've set a username or not.
Migration Guide for Developers
Here's the step-by-step process to migrate your existing WhatsApp automation from phone numbers to BSUID.
Step 1: Audit Your Codebase
Find every place you're hardcoding phone number formats or assuming phone numbers are stable identifiers.
Search patterns:
# Grep for phone number regex patterns
grep -r '\d{10,}@c\.us' .
# Find database queries using phone numbers as keys
grep -r 'WHERE phone =' .
grep -r 'JOIN.*phone' .
# Find webhook handlers parsing 'from' field
grep -r 'req.body.from' .Common places to check:
- Webhook handlers (where you receive incoming messages)
- Database schemas (user tables, conversation logs)
- Message sending functions (where you specify
chatId) - Contact matching logic (finding users by phone number)
- Conversation threading (grouping messages by sender)
Step 2: Update Webhook Handlers
The biggest breaking change is in webhook payloads. The from field will switch from phone numbers to BSUID after June 2026.
Before: Assuming phone number in from field
app.post('/webhook', (req, res) => {
const phoneNumber = req.body.from; // "[email protected]"
const user = db.users.findByPhone(phoneNumber);
if (!user) {
// Create new user with phone as primary key
user = db.users.create({ phone: phoneNumber });
}
// Handle message...
handleMessage(user, req.body.message);
res.status(200).send('OK');
});Problem: After June 2026, req.body.from will be bsuid:ABC123..., not a phone number. Your findByPhone() query will fail.
After: Handle BSUID or phone number
app.post('/webhook', (req, res) => {
const senderId = req.body.from; // Could be "bsuid:ABC123" or "[email protected]"
const phoneNumber = req.body.phone; // Phone number (always present for backward compat)
const username = req.body.username; // Username if user has set one (optional)
// Try to find user by BSUID first (most reliable)
let user = db.users.findBySenderId(senderId);
if (!user) {
// Fallback: try phone number (for existing records created pre-migration)
user = db.users.findByPhone(phoneNumber);
if (user) {
// Update existing record with BSUID
db.users.update(user.id, { bsuid: senderId });
} else {
// Create new user with both BSUID and phone
user = db.users.create({
bsuid: senderId,
phone: phoneNumber,
username: username
});
}
}
// Handle message...
handleMessage(user, req.body.message);
res.status(200).send('OK');
});Key changes:
- Look for BSUID first (new primary identifier)
- Fall back to phone number for existing users
- Update existing records to include BSUID when you see them
- Store username if available
Step 3: Update Database Schema
Add columns for BSUID and username. Don't remove the phone number column yet — you'll need it for backward compatibility during the transition.
Database migration (SQL):
-- Add BSUID column to contacts/users table
ALTER TABLE contacts ADD COLUMN bsuid VARCHAR(255);
ALTER TABLE contacts ADD COLUMN username VARCHAR(50);
-- Create indexes for fast lookups
CREATE INDEX idx_contacts_bsuid ON contacts(bsuid);
CREATE INDEX idx_contacts_username ON contacts(username);
-- Make BSUID the preferred identifier (but don't enforce NOT NULL yet)
-- You'll have a mix of old (phone-only) and new (BSUID + phone) recordsMigration strategy:
- Phase 1 (Now - May 2026): Add BSUID/username columns, populate them opportunistically
- Phase 2 (June 2026): Enforce BSUID as primary key for all new records
- Phase 3 (July-Aug 2026): Backfill missing BSUIDs for old records (if possible)
- Phase 4 (Sept 2026+): Deprecate phone-number-only lookups
Don't try to do this in one big-bang migration. Update your code to handle both formats, then gradually shift from phone to BSUID as the primary identifier.
Step 4: Update Message Sending Logic
Anywhere you're sending messages, update the code to accept BSUID, phone number, or username.
Before:
def send_message(phone_number, text):
response = requests.post(
'https://apiv2.waiflow.app/api/v2/messages',
headers={'Authorization': f'Bearer {API_TOKEN}'},
json={
'session_name': 'my-session',
'chatId': phone_number, # Assumes phone number format
'text': text
}
)
return response.json()After:
def send_message(contact_id, text):
"""
contact_id can be:
- BSUID: bsuid:ABC123...
- Phone: [email protected]
- Username: @alice
"""
response = requests.post(
'https://apiv2.waiflow.app/api/v2/messages',
headers={'Authorization': f'Bearer {API_TOKEN}'},
json={
'session_name': 'my-session',
'chatId': contact_id, # MoltFlow accepts all three formats
'text': text
}
)
return response.json()MoltFlow's API already handles all three identifier types interchangeably. You just need to pass whatever identifier you have.
Step 5: Update Contact Search
If you're searching for contacts by phone number, update to support BSUID and username search.
Before:
curl "https://apiv2.waiflow.app/api/v2/contacts/search?phone=1234567890" \
-H "Authorization: Bearer YOUR_API_TOKEN"After:
# Search by BSUID
curl "https://apiv2.waiflow.app/api/v2/contacts/search?bsuid=bsuid:ABC123..." \
-H "Authorization: Bearer YOUR_API_TOKEN"
# Search by username
curl "https://apiv2.waiflow.app/api/v2/contacts/search?username=alice" \
-H "Authorization: Bearer YOUR_API_TOKEN"
# Search by phone (still works for backward compatibility)
curl "https://apiv2.waiflow.app/api/v2/contacts/search?phone=1234567890" \
-H "Authorization: Bearer YOUR_API_TOKEN"Response (all three methods return same structure):
{
"contacts": [
{
"id": "[email protected]",
"bsuid": "bsuid:ABC123DEF456",
"username": "alice",
"name": "Alice Smith",
"isMyContact": true,
"profilePicUrl": "https://..."
}
]
}Step 6: Test with Both Formats
Before June 2026, WhatsApp will send both phone numbers and BSUIDs in webhook payloads (transition period). Use this time to test your code handles both.
Test webhook payload (transition period):
{
"event": "message",
"from": "bsuid:ABC123DEF456",
"phone": "[email protected]",
"username": null,
"message": {
"text": "Test message"
}
}Test checklist:
✅ Webhook handler processes BSUID correctly
✅ Existing users (phone-only in DB) get updated with BSUID
✅ New users get created with both BSUID and phone
✅ Message sending works with BSUID as chatId
✅ Contact search returns both BSUID and phone
✅ Conversation threading uses BSUID as primary key
How MoltFlow Handles BSUID Automatically
Good news: if you're using MoltFlow, most of the complexity is abstracted away. Here's what MoltFlow does automatically:
1. Dual Identifier Support
MoltFlow's session management already handles phone numbers, BSUIDs, and usernames interchangeably. When you send a message, you can use any identifier:
# All three work identically
curl -X POST https://apiv2.waiflow.app/api/v2/messages \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-d '{ "chatId": "[email protected]", ... }'
curl -X POST https://apiv2.waiflow.app/api/v2/messages \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-d '{ "chatId": "bsuid:ABC123...", ... }'
curl -X POST https://apiv2.waiflow.app/api/v2/messages \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-d '{ "chatId": "@alice", ... }'MoltFlow resolves all three to the correct recipient internally.
2. Enriched Webhook Payloads
MoltFlow's webhooks include both BSUID and phone number in every payload during the transition period:
{
"event": "message",
"from": "bsuid:ABC123DEF456",
"phone": "[email protected]",
"username": "alice",
"message": {
"text": "Hello"
}
}This gives you time to update your code without breaking existing logic. Your webhook handler can continue using phone for now, then gradually shift to from (BSUID).
3. Contact Metadata Includes All Identifiers
When you fetch contact information, MoltFlow returns all available identifiers:
curl https://apiv2.waiflow.app/api/v2/contacts/search?query=alice \
-H "Authorization: Bearer YOUR_API_TOKEN"Response:
{
"contacts": [
{
"id": "[email protected]",
"bsuid": "bsuid:ABC123DEF456",
"username": "alice",
"name": "Alice Smith",
"isMyContact": true
}
]
}You can store whichever identifier makes sense for your use case (we recommend BSUID as primary key, phone as backup).
4. Automatic Fallback Logic
If you send a message to a phone number and MoltFlow detects that user has migrated to BSUID-only, it automatically resolves the phone to the correct BSUID. You don't need to update your database immediately — MoltFlow handles the mapping.
Timeline & Action Items
Here's the migration roadmap:
Now through May 2026: Preparation Phase
- ✅ Add BSUID and username columns to your database
- ✅ Update webhook handlers to process both phone and BSUID
- ✅ Test your code with mock BSUID payloads
- ✅ Update message sending functions to accept multiple identifier types
- ✅ Document which parts of your codebase assume phone numbers
June 2026: Migration Deadline
- Meta starts issuing BSUID in all new conversations
- Phone numbers remain available in webhooks but are secondary
- Existing conversations (started pre-June) may continue using phone numbers for a transition period (exact duration unclear)
July-August 2026: Backfill Period
- Update existing user records with BSUIDs as you encounter them
- Monitor for any lingering phone-number-only code paths
- Migrate primary keys from phone to BSUID gradually
September 2026+: BSUID-First
- Treat BSUID as the canonical identifier
- Phone numbers become optional metadata
- Usernames become common (but still optional)
Recommendation: Aim to complete your migration by May 15, 2026 — two weeks before the deadline. This gives you buffer time for unexpected issues.
What's Next?
BSUID is mandatory by June 2026. Usernames are optional but useful for user privacy and convenience.
Three things to do this week:
- Audit your codebase — Find everywhere you're using phone numbers as stable identifiers
- Add BSUID columns — Update your database schema to support the new identifier
- Test webhook handlers — Verify they handle both phone and BSUID correctly
Migration resources:
- REST API Quick Start — Update to BSUID-compatible endpoints in 5 minutes
- Set Up Webhooks — Configure webhook handlers for BSUID format
- Complete MoltFlow API Guide — Full authentication and endpoint reference
- Getting Started with WhatsApp Automation — Basic setup guide
Need help migrating? MoltFlow handles BSUID automatically — no code changes required. Our API abstracts the complexity so you can use phone numbers, BSUIDs, or usernames interchangeably. Start your 14-day free trial and test BSUID compatibility before the June deadline.
The shift from phone numbers to BSUID is Meta's way of improving privacy and enabling new features like usernames. For developers, it means updating your code to handle opaque identifiers instead of phone numbers. Start now, test thoroughly, and you'll breeze through the June deadline without breaking production.
> Try MoltFlow Free — 100 messages/month