Chapter 4 / 9
How data gets into Amplitude, and where it goes
Data enters Amplitude through several doors, but all of it is written to the same event model and the same user identity. Choosing the door is almost exactly the SDK vs S2S decision you know from Adjust.
- You will know when to use the SDK, the HTTP API, integrations and the warehouse.
- You can read the basic code patterns for mobile, web and backend.
- You will know how to avoid double counting when the same signal travels two routes.
The overall flow
SDK and S2S: the same split in both tools
The rule you know from Adjust applies here too: behavioural events come from the device; verified money events and events that happen while the app is closed come from the server.
| Event type | Recommended route | Why |
|---|---|---|
| Screen views, taps, searches | SDK | Session and device context come automatically |
| Onboarding steps | SDK | Anonymous behaviour before login is measured |
| Purchase (verified) | HTTP API, after the backend verifies it | Fake or cancelled transactions are not counted |
| Subscription renewal, refund | HTTP API | Happens when the user is not in the app |
| Status change from the CRM (e.g. plan upgrade) | HTTP API or an identify call | The backend is the source |
If you send the same purchase as Purchase from the SDK and Purchase from the backend, you get two events. Either pick one route or separate the names (Purchase Attempted from the device, Purchase Completed from the server). On the server side, give every event a unique insert_id to protect against resends.
Code patterns
Every SDK has the same four basic operations: initialise, send an event, identify the user, update user properties. The examples below show these four side by side.
import * as amplitude from '@amplitude/analytics-browser';
// 1. Initialize once, as early as possible
amplitude.init('YOUR_API_KEY');
// 2. Track an event with properties
amplitude.track('Product Viewed', {
product_id: 'SKU-1042',
category: 'headphones',
price: 49.9,
});
// 3. Identify the user after login
amplitude.setUserId('cust_88231');
// 4. Update persistent user properties
const identify = new amplitude.Identify();
identify.set('plan', 'premium');
identify.setOnce('signup_date', '2026-11-04');
identify.add('orders_count', 1);
amplitude.identify(identify);
// Revenue as a dedicated call
const revenue = new amplitude.Revenue()
.setProductId('SKU-1042')
.setPrice(49.9)
.setQuantity(1);
amplitude.revenue(revenue);import { init, track, setUserId, identify, Identify } from '@amplitude/analytics-react-native';
// 1. Initialize once at app start
init('YOUR_API_KEY');
// 2. Track an event
track('Product Viewed', { product_id: 'SKU-1042', category: 'headphones', price: 49.9 });
// 3. Identify after login
setUserId('cust_88231');
// 4. User properties
const event = new Identify();
event.set('plan', 'premium');
identify(event);import AmplitudeSwift
// 1. Initialize once, e.g. in AppDelegate
let amplitude = Amplitude(configuration: Configuration(apiKey: "YOUR_API_KEY"))
// 2. Track an event
amplitude.track(eventType: "Product Viewed",
eventProperties: ["product_id": "SKU-1042", "price": 49.9])
// 3. Identify after login
amplitude.setUserId(userId: "cust_88231")
// 4. User properties
let identify = Identify()
identify.set(property: "plan", value: "premium")
amplitude.identify(identify: identify)import com.amplitude.android.Amplitude
import com.amplitude.android.Configuration
import com.amplitude.core.events.Identify
// 1. Initialize once, e.g. in Application.onCreate
val amplitude = Amplitude(Configuration(apiKey = "YOUR_API_KEY", context = applicationContext))
// 2. Track an event
amplitude.track("Product Viewed", mutableMapOf("product_id" to "SKU-1042", "price" to 49.9))
// 3. Identify after login
amplitude.setUserId("cust_88231")
// 4. User properties
amplitude.identify(Identify().set("plan", "premium"))# Server-side event after the backend has verified the renewal
curl -X POST https://api2.amplitude.com/2/httpapi \
-H 'Content-Type: application/json' \
-d '{
"api_key": "YOUR_API_KEY",
"events": [{
"user_id": "cust_88231",
"event_type": "Subscription Renewed",
"time": 1762243200000,
"insert_id": "renewal-cust_88231-2026-11",
"event_properties": { "plan": "premium", "period": "monthly" },
"revenue": 9.99
}]
}'SDK package names, method signatures and init options change between versions; the gap between legacy and newer-generation SDKs is especially large. Check the documentation for the SDK version in the project before using these in a client integration. The HTTP API uses a different endpoint for EU data residency, and there is a separate batch endpoint for large volumes.
Things to watch with server events
- Identity: to tie a server event to the right person, send the same
user_idvalue as on the device. Only logged-in users' server events attach correctly. - Time: the
timefield is when the event happened (in milliseconds), not when you sent it. - Resends: use the same
insert_idon retries; Amplitude counts the sameinsert_idas one event within a certain window. - Sessions: server events are not part of the user's app session and are not expected to affect metrics such as session length.
In Adjust S2S you need device identifiers (ADID, IDFA/GAID, etc.) to tie an event to the right device; an S2S event without an identifier doesn't match. In the Amplitude HTTP API the equivalent is user_id or device_id. If the same backend sends to both tools, it has to store both sets of identifiers together.
When do SDK events show up?
SDKs don't send events instantly; they queue them on the device and send in batches once a count or a time limit is reached. Without internet they wait. So an event can take seconds to reach Amplitude, or hours in an offline scenario, but the time field always carries the moment it happened.
When testing an integration, use user lookup and the ingestion debugger instead of looking for the event in charts. They are the equivalent of the Adjust Testing Console and show almost immediately whether an event arrived and with which properties.
Environments: where is sandbox?
In Adjust, sandbox and production live inside the same app and are separated by a filter. In Amplitude the common practice is separate projects for development and production. Each project has its own API key, and the build configuration decides which key is used.
A development build shipping with the production API key. Test events mix with real data and are hard to clean up. Set up a structure that picks the key per build environment and check it before release.
Working with a warehouse
If data already lives in a warehouse (e.g. backend events in BigQuery), Amplitude can import it. In the other direction, raw Amplitude events can be exported to a warehouse for analysis and backup. This is where Amplitude overlaps most with Ryze's API-to-warehouse connector work.
Supported warehouse sources and destinations, sync frequency and plan availability can change. Check the current list in Amplitude's integration catalogue.
Create a free test project. Add the Web SDK to a simple HTML page, wire track to a button and setUserId to a "log in" button. Then send a server event for the same user from the terminal with the HTTP API example above. Confirm in user lookup that all three events appear in the same person's stream.