For AI agents: a documentation index is available at /docs/llms.txt. Append .md to any page URL for markdown, or send Accept: text/markdown.
Verify Browser SDK 2 events
이 페이지는 아직 귀하의 언어로 번역되지 않았습니다. 현재 작업 중이므로 곧 다시 확인해 주십시오.
An event moves through four stages between your track() call and Amplitude's servers. Verify each stage in order. The first stage where the event looks wrong is where the problem is.
| Stage | Question | Where to look |
|---|---|---|
| Captured | Did the SDK accept the call? | Debug logs in the browser console |
| Transformed | What did plugins add, change, or drop? | Verbose logs, or an inspection plugin |
| Sent | Did the request leave the browser, and with what payload? | The browser Network tab |
| Received | Did Amplitude accept and store the event? | The upload result, then User Lookup in Amplitude |
For problems common to every Amplitude SDK, go to SDK troubleshooting and debugging. If behavior changed after an SDK upgrade, check the Browser SDK 2 release notes.
Turn on debug logging
Set logLevel to Debug while you verify. Debug logs include every public method call, the internal state before and after it, the final event, and the server response.
amplitude.init("YOUR_API_KEY", {
logLevel: amplitude.Types.LogLevel.Debug,
});
In environments where you can't import the enum, such as Google Tag Manager, use the number 4. The levels are 0 None, 1 Error, 2 Warn (default), 3 Verbose, and 4 Debug. Each message starts with Amplitude Logger, so filter the console on that prefix.
Use numbers in Google Tag Manager
Don't pass a string such as "LogLevel.None". The SDK compares logLevel as a number, so a non-numeric string turns on every level, including Debug.
Return logLevel to Warn before you ship. Debug output includes event payloads and identifiers.
Check what the SDK captured
With debug logging on, each public call prints a block like this:
Amplitude Logger [Debug]: {
"type": "invoke public method",
"name": "track",
"args": ["Sign Up Clicked", { "plan": "starter" }],
"stacktrace": ["at handleClick (app.js:42:13)", "..."],
"time": { "start": "2026-10-26T12:00:11.402Z", "end": "2026-10-26T12:00:12.611Z" },
"states": {
"before": { "config.apiKey": "...", "timeline.queue.length": 0 },
"after": { "config.apiKey": "...", "timeline.queue.length": 0 }
}
}
For methods that return a promise, such as track(), identify(), and flush(), the SDK prints the block after the promise resolves. A track block therefore appears after the upload attempt, about one second after the call.
Confirm three things:
- The call ran. A
trackblock appears. If not, either your code didn't reach the call orinit()never ran on the instance you called. A second instance fromcreateInstance()needs its owninit(). To confirm when the page exposes theamplitudeglobal (the script loader does), runamplitude.init("YOUR_API_KEY")in the browser console. If queued events then send, your owninit()call didn't run, or it ran on a different instance. - The arguments are right.
argsshows the event name and properties exactly as the SDK received them. - The SDK was ready. Calls made before
init()completes don't fail. They wait in a queue and run afterinit(), so a call that appears early in the log can still send later.
At this stage you can also read the state the SDK attaches to every event:
amplitude.getDeviceId(); // "f3b6c1e2-..."
amplitude.getUserId(); // "user-12345" or undefined
amplitude.getSessionId(); // 1761480000000
amplitude.getOptOut(); // false
If getOptOut() returns true, the SDK drops every event before any plugin runs. getOptOut() requires Browser SDK 2.18.0 or later.
Check how plugins transformed the event
After capture, each event passes through the plugin pipeline in a fixed order:
- Before plugins (
type: "before"): The built-in context plugin addsdevice_id,user_id,session_id,time,event_id,insert_id,library,user_agent,platform,language, andip. The network checker pauses uploads while the browser is offline. - Enrichment plugins (
type: "enrichment", the default): Autocapture plugins add properties here. For example, page URL enrichment adds[Amplitude] Page URLand[Amplitude] Previous Page Location, and attribution adds campaign parameters. Your own enrichment plugins also run here, in the order you add them. - Destination plugins (
type: "destination"): The built-inamplitudedestination queues the event for upload.
A before or enrichment plugin that returns null drops the event. To find the plugin responsible for a change or a drop, set logLevel to Verbose or Debug. The SDK then logs each step:
Amplitude Logger [Log]: Timeline.apply: Event after enrichment plugin 'my-plugin', event: {...}
Amplitude Logger [Log]: Timeline.apply: Event filtered out by enrichment plugin 'my-plugin', event: {...}
Amplitude Logger [Log]: Timeline.apply: Final event before destinations, event: {...}
The Final event before destinations line is the exact event the SDK sends. Compare it with what you expect.
To inspect events in code, for example in an automated test, add a destination plugin that records each event without sending it anywhere:
const captured: amplitude.Types.Event[] = [];
amplitude.add({
name: "inspect-events",
type: "destination",
execute: async (event) => {
captured.push(structuredClone(event));
return { event, code: 200, message: "inspected" };
},
});
Destination plugins receive the event after every before and enrichment plugin has run. Remove the plugin with amplitude.remove("inspect-events") when you're done.
Check what the SDK sent
The SDK uploads events with a POST request. Open the browser Network tab and filter on the host for your data center.
| Configuration | Request URL |
|---|---|
| Default (US) | https://api2.amplitude.com/2/httpapi |
serverZone: "EU" | https://api.eu.amplitude.com/2/httpapi |
useBatch: true (US) | https://api2.amplitude.com/batch |
useBatch: true (EU) | https://api.eu.amplitude.com/batch |
serverUrl set | The URL you set |
Requests leave the browser flushIntervalMillis (1 second by default) after the first event enters the queue. Each request carries up to flushQueueSize (30 by default) events. To send right away, call await amplitude.flush().promise.
The request body has this shape:
{
"api_key": "YOUR_API_KEY",
"events": [
{
"event_type": "Sign Up Clicked",
"event_properties": { "plan": "starter" },
"device_id": "f3b6c1e2-...",
"session_id": 1761480000000,
"time": 1761480012345,
"event_id": 4,
"insert_id": "9c0e...",
"platform": "Web",
"language": "en-US",
"ip": "$remote",
"library": "amplitude-ts/2.48.1",
"user_agent": "Mozilla/5.0 ..."
}
],
"options": {},
"client_upload_time": "2026-10-26T12:00:12.400Z"
}
"ip": "$remote" tells Amplitude to use the request's IP address. Set trackingOptions.ipAddress to false to omit it.
To watch events without opening DevTools, install the Amplitude Instrumentation Explorer Chrome extension. It lists each event the page sends, with its properties.
Compressed request bodies
For Amplitude's own endpoints, the SDK gzip-compresses request bodies of 2 KB or more when the browser supports CompressionStream. Those requests carry a Content-Encoding: gzip header, and the Payload tab shows binary data. To read the events, use the Final event before destinations log line or an inspection plugin instead. This default changed in 2.42.0.
If no request appears:
| Cause | How to confirm | Fix |
|---|---|---|
| The browser is offline | navigator.onLine is false. Debug logs show Skipping flush while offline. | The SDK uploads automatically when the browser reconnects. |
| An ad blocker or privacy extension blocks the request | The request shows (blocked:other) or net::ERR_BLOCKED_BY_CLIENT in Chrome. Firefox names the blocking extension in the Transferred column. Safari doesn't list the request. | Route events through a proxy. |
| A Content Security Policy blocks the request | The console shows Refused to connect to .... | Add https://*.amplitude.com to connect-src. |
| The page unloaded before the request finished | The request shows (canceled). | Go to Send events when the page unloads. |
transport is "beacon" | The request has type ping, and the response is unavailable. | Expected. sendBeacon doesn't expose responses. Switch to "fetch" while you verify. |
Check what Amplitude received
Read the upload result
Every tracking method returns { promise }. The promise resolves to a Result after the upload attempt finishes:
const { event, code, message } =
await amplitude.track("Sign Up Clicked").promise;
event is the final event as sent. code and message come from the server response or from the SDK:
code | message | Meaning | What the SDK does |
|---|---|---|---|
200 | Event tracked successfully | Amplitude accepted the event. | Removes the event from the queue. |
400 | Server error text, for example Invalid API key: ... | Amplitude rejected the event or the whole request. | Drops events the server marked invalid, and retries the rest. |
400 | Event rejected due to missing API key | init() received an empty API key. | Drops the event. |
413 | Server error text | The request was too large. | Halves flushQueueSize and retries. Drops a single event that's still too large. |
429 | Server error text | The user or device exceeded a rate limit. | Drops events for users or devices over their daily quota. Retries throttled events after 30 seconds. |
500 | Event rejected due to exceeded retry count | The upload failed flushMaxRetries (5) times. Network errors, blocked requests, and server errors end here. | Drops the event. |
0 | Event skipped due to optOut config | Opt-out is on. | Never sends the event. |
0 | Event skipped due to empty event type | The event name is empty. | Never sends the event. |
100 | Event not tracked, no destination plugins on the instance | You removed the amplitude destination. | Never sends the event. |
A successful response body looks like this:
{
"code": 200,
"events_ingested": 1,
"payload_size_bytes": 412,
"server_upload_time": 1761480012612
}
A 200 response means Amplitude's ingestion API accepted the event. Amplitude can still exclude an accepted event from analysis later, for example when a block filter matches it.
Manual flushes don't retry
amplitude.flush() sends every queued event once and doesn't retry failures. Each event's promise resolves with the response status and body as the message, for example success: {"code": 200, ...} or invalid: {...}. Scheduled uploads retry and use the messages in the table.
Find the event in Amplitude
- Open User Lookup and search for the event's
user_id, or itsdevice_idif the user is anonymous. - Select the Activity tab and turn on Live event updates.
- Open the event and compare its properties with the
Final event before destinationslog line.
For events from many users, use the Ingestion Debugger to review accepted and rejected requests for the project. Amplitude's chart queries can take a few minutes longer to include new events than User Lookup does.
이 내용이 도움이 되었나요?