Written by Facundo Piaggio (Solutions Architect)
We’ve all been there. You look at your dashboard and see 10,000 “Monthly Active Users,” but your database says you only have 5,000. Where did these ghost users come from? Usually, it’s because your identity strategy is more of a “suggestion” than a rule, and your stack is paying the price for it.
Getting user identity right across Braze and Amplitude is one of the most underrated infrastructure decisions a growth team can make. If you’re an engineer or growth practitioner trying to orchestrate world-class lifecycles in Braze and perform deep-dive behavioral analysis in Amplitude, you need to speak their language. That language is written in IDs and here’s how to get it right.
To keep your sanity, Amplitude’s user_id must match Braze’s external_id — exactly, every time.
When these two values align, your tech stack becomes a unified ecosystem. You can see a user drop off in an Amplitude funnel and immediately trigger a Braze “abandoned cart” push notification — because both tools recognize User_123 as the same human being.
Amplitude doesn’t just rely on your database ID; it uses a tiered system to ensure a user’s journey isn’t fragmented across devices.
The Trinity: Amplitude tracks every user through a combination of amplitude_id (internal), device_id, and your user_id.
The Merge: If a user browses anonymously on their iPhone (device_A) and later logs in as user_123, Amplitude maps that device_id to the user_id. If that same user later logs in on an iPad (device_B), Amplitude recognizes user_123 and merges all activity into one single profile.
external_id or BustBraze operates similarly but with higher stakes — because it handles direct communication with your users. While Braze tracks many identifiers, the external_id is the only one that truly matters for identification.

changeUser() RequirementThe Braze SDK latches onto a profile once it’s loaded. To switch from an anonymous guest profile to a known user profile, your application must call:
changeUser(external_id)
⚠️ The catch: Your application must have access to that ID before it can tell Braze who the user is. If the external_id isn’t available, the switch can’t happen.
This becomes even more critical in edge cases like shared devices. Without the external_id, it’s impossible to force the Braze SDK to switch the user it’s internally targeting with attributes and events, the SDK stays locked to the first user it encountered.
In these scenarios, you’re left with two unappealing options: profile merging (which risks overwriting critical data) or calling the wipeData() method. Be warned — wipeData() often introduces a whole new set of headaches around data persistence and session tracking.
You could technically ignore changeUser() and patch things up later using the /users/merge API endpoint. Here’s why that approach backfires:
The Triple-Flow Trap: You have to perfectly manage logic for three separate scenarios — Login, Registration, and App Startup. If the logic breaks in even one, you create duplicate profiles.
Rate Limits: At scale, hitting an API to merge profiles manually will quickly trigger rate limits, leading to data gaps.
Syncing Lag: Manual merges aren’t always instantaneous. Your “Welcome” email might fire to the old duplicate profile before the merge completes.
You should establish a mechanism to identify anonymous users so that when a profile moves through any of the above flows, a request is made to the /users/merge endpoint.
One more thing: you must execute this endpoint call from a back-end environment. For security reasons, Braze doesn’t allow API calls directly from the front-end, so the front-end must transmit the merging data to an internal service first. That means you also need to account for the scalability of that internal service, especially if you’re handling a high volume of users or concurrent sessions.
Stop trying to fix identity after the data is already messy.
setUserId in Amplitude and changeUser in Braze as soon as the ID is available.