Time and the offline queue
How the server corrects skewed device clocks and how long an event can wait to be sent.
A phone's clock can be hours ahead or stuck in 1970, and an event can sit in the queue for several days without a network. The server handles both cases as long as the client reports time correctly.
Two timestamps in a batch
- the event's
timestamp— when the event happened, by the device clock; - the batch's
sent_at— when the HTTP request was sent, by the same clock.
Both fields are RFC 3339 with an explicit offset (Z or +03:00), preferably with milliseconds. A
string without an offset is rejected with the code invalid_field. Set sent_at anew on every send
attempt, including when you retry a batch. An example of sending late events from a terminal is in
the curl recipe.
Clock correction
The server doesn't trust the device's absolute time, but it does trust the difference between two of its timestamps:
event_time = received_at + (timestamp − sent_at)received_at is the time the request was received, by the server clock. If the device clock is
3 hours fast, both timestamps are shifted by the same amount, and the difference stays correct.
| Situation | Result |
|---|---|
event_time is more than 10 minutes after received_at | The event is accepted with event_time = received_at and the warning timestamp_in_future_clamped |
event_time is more than 7 days before received_at | The event is rejected with the code event_too_old |
| Exactly 7 days | Accepted: the boundary is inclusive |
The original timestamp is kept as client_time and is visible in the profile.
Offline queue
Without a network, events pile up on the device and are sent later. Requirements for the queue:
- Store it on disk, not in memory: the app may be killed while in the background.
- Send in batches of up to 500 events and 1 MiB: on app start, on
session_startand when the network comes back. - Drop events older than 7 days before sending: the server would reject them anyway.
- Remove events from the queue only after a response. What to do for each response code is described in Errors and retries.
- Retry the same batch with the same
insert_idvalues. Duplicates don't inflate metrics, so a retry after a timeout is safe even if the first request got through.
The per-device limit is 1,000 events at once, then 100 per minute. That's enough to send a week-long
queue. Whatever doesn't fit into the limit, the server returns in rejected with retryable: true:
keep those events in the queue and send them a minute later.
Late events and report days
An event that arrives several days late lands on the day it happened. That's why the numbers for past
days can grow slightly after the fact, within the 7-day window for late events. After that, events
are rejected as event_too_old, and the day's numbers no longer change, with two exceptions: changing
the app's time zone recalculates every past day in the new zone, and deleting a person's data
recalculates the days they were active on. Both are done by the next day-closing run, every 15
minutes.