1. How do events get in, and which door should our backend use
There are two, and they are not the same door. The collector takes a write key and de-duplicates; the management API takes a management key and does not.
| Collector | Management API | |
|---|---|---|
| Endpoint | POST /v1/batch | POST /v1/events |
| Credential | write key, wk_seg_... | API key, sk_seg_... |
| De-duplicates on message_id | Yes | No |
| Returns warnings | Yes | No, they are discarded |
| Backdating limit | The tenant's own retention | Fixed 30 days |
| Maximum items | 500 | 500 |
| Costs request budget | No | Yes |
Use the collector from your backend. The reason is de-duplication rather than preference: your order pipeline will retry, and a timeout at the load balancer, a redeploy mid-request or a worker that dies after the POST all end with the same batch arriving twice. The collector records the message id and answers the second delivery without storing it again. The management API hands envelopes straight to the queue, so a retried batch of a hundred orders becomes two hundred, and the first sign of it is a revenue figure nobody can reconcile.
2. How is one person identified across two devices
There are two identifiers: an anonymous id the SDK creates and keeps, and the id you supply when you know who somebody is. Calling identify links them, so the behaviour recorded before sign-in belongs to the same person afterwards.
What the documentation is careful to say, and what most platforms leave vague, is what does not happen. Linking is not retroactive stitching of an entire history across every device somebody has ever used, and two people sharing one device is a case with its own answer rather than an accident. Read that page before you design your identify calls, because the failure mode is a profile that quietly merges two humans.

3. What happens when the same event arrives twice
On the collector, the second one is answered successfully and never reaches the warehouse, keyed on the message id you send, within a fixed window. That is the whole reason to prefer it from a backend.
On the management API, nothing happens: the duplicate is stored. If your integration already holds a management key and adding a second credential is the harder problem, that is a legitimate reason to use it, and you are then responsible for not sending the same event twice.
4. What are the real limits
Read them from the server rather than from a page. There is a capabilities endpoint that reports what this installation supports and what it limits, and reading it at start-up is the documented way to stay correct when a limit changes.
Two of them surprise people and are worth naming here. There are silent ceilings: some values are truncated rather than refused, so the request succeeds and the data is not what you sent. And there is a timestamp window: an event dated outside it is not rejected, it is moved, which means a backfill can quietly land on the wrong day. Both are documented on the limits page with the exact behaviour.

5. How does push work on Android here
Not through Firebase alone, and this is the constraint that most surprises a team arriving from outside Iran. On a large share of devices Google Play Services is missing or unreachable, and Firebase then delivers nothing while your send succeeds.
A device can carry tokens for FCM, Cafe Bazaar and Myket at the same time, and the campaign selects a route that can reach that device. Your app supplies each token, so the Android SDK does not force a Firebase or store-library version into your project. It has no third-party runtime library beyond Kotlin's standard library and adds only the INTERNET permission.
6. What happens when the API changes
The version is one segment of the path and nowhere else, a breaking change is announced at least six months ahead, and a retired version answers for at least a year after its successor ships. Adding a key to a response is not breaking, so your parser must ignore unknown fields and send unknown enum values to a default branch.
One thing is stated plainly rather than implied: no machine-readable deprecation headers are sent today. No Sunset header, no Deprecation header, no Warning. The signals are the documentation changelog and an email to the technical contact of accounts that actually called the endpoint. If you write code expecting a header before something is switched off, that header never arrives.
The reference pages behind each answer above:
- Sending events from your serverThe two doors, de-duplication, partial failure and batch sizes.
- IdentityThe two identifiers, what identify does, and what it deliberately does not.
- LimitsReading them from the server, the silent ceilings and the timestamp window.
- Devices and push transportsThe four Android transports and who supplies the token.
- API versioningWhat breaks an integration, what does not, and how much notice you get.
- QuickstartThe shortest path from nothing to a first event.
Questions you may have about this
Should our backend use the collector or the management API
The collector, POST /v1/batch. It de-duplicates on the message id you send, and a backend pipeline will retry. The management API does not de-duplicate, so a retried batch is stored twice.
Is the write key safe to ship in our app
Yes, it is public by design and the platform assumes anyone can read it. The management key is the opposite and must never leave your servers. Presenting a write key to the management API is refused with a distinct error code so the mistake is visible.
Do you have Android and iOS SDKs
The Android SDK is available as source and integrates through Gradle. It covers events, identity, push registration and in-app messages. There is no packaged iOS SDK yet, so iOS uses the documented REST endpoints without waiting for a client package.
How much notice do we get before a breaking API change
At least six months, announced in the documentation changelog and by email to the technical contact of every account that called that endpoint. A retired version keeps answering for at least a year after its successor ships.
Do you send Sunset or Deprecation headers
No, none today. That is written down deliberately, because code that waits for a machine-readable warning before an endpoint is switched off will wait forever.
