account:write. A device belongs to whoever is signed into it, which is why
it lives under /v1/me with the rest of what an account owns.
Just send it every time
Registering is an upsert, not a create. The app has no durable handle on a registration: it is reinstalled, restored onto a new phone, or simply handed a fresh token, and in none of those cases does it know the id we gave it last time. So the push token decides — the same token updates the row it already owns (200),
a new one creates a row (201). You do not have to remember an id, check whether
you registered before, or clean up after a reinstall.
A token that already belongs to another user is taken over rather than refused.
The token addresses a handset, and whoever signed in on it last is who should
receive its notifications; leaving it where it was would push the previous owner’s
alerts to a phone that is no longer theirs.
The rest
pushToken is write-only and never comes back in a response: the app already has
it, and anything else reading it could push to somebody else’s device. It is set only
by POST: a PATCH changes appId, appVersion, name, locale, platform or
apnsEnvironment, and one carrying pushToken is refused with 422 — a new token is
a new registration.
GET, PATCH and DELETE reach only your own devices — somebody else’s answers
404, not 403, so the ids cannot be used to count installs. On sign-out, DELETE
the registration if you kept the id; otherwise simply stop sending, and it moves
along with the token the next time someone signs in on that handset.
Sending is built. Notifications go direct to APNs and FCM — no third-party push
service — and every one about the person themselves also lands in the bell at
/v1/me/notifications, because a push arrives or it does not and somebody who missed one
has no way back to it. A shop’s news does not: see below.The bell, as the website’s inbox has it: filter[unread]=true (or false) narrows the list,
PATCH /v1/me/notifications/{id} marks one read, POST /v1/me/notifications/read all of
them, DELETE /v1/me/notifications/{id} clears one and DELETE /v1/me/notifications the
whole bell — the website’s “delete all”.A registration the platform tells us is dead is deleted on the spot, so a row
disappearing during testing is the cleanup working rather than a bug.Send apnsEnvironment with an iOS registration if you can read it (development or
production, from the provisioning profile). A simulator build is on Apple’s sandbox and an
internal build is on production — Ad Hoc provisioning signs with
aps-environment=production however much the build is called “development” — so there is no
one setting for us to get right. Without it, the first notification probes: the default is
tried, the other host is tried once if Apple refuses the token, and the answer is stored.Send appId with every registration: the bundle id / package of this build
(eu.wristler.app, eu.wristler.app.staging or eu.wristler.app.dev). The three apps share
one Firebase project, so the token does not say which app it is. A server that is not
production pushes only to devices of its own app and logs the rest — a device without appId
included — so staging can never reach somebody’s store app. Production pushes to every device.What to do with a push: route on data.type and the ids beside it, never on the words —
they are translated, and they come in the language of locale on this registration
rather than the language of the account. data.notificationId is the bell entry it
belongs to, so opening a push can mark the right item read.What arrives
Every push carries, indata:
type: the category, the switch in the notification preferences that governs it;event: what happened, from the table below — the bell calls the same wordevent;- the ids its screen needs, all strings, and only the ones that event has;
notificationId: the bell entry, on everything about the person themselves.
event and the ids; fall back on type for an event you do not know yet, and
on the ids for one with no screen of its own. Never on the words.
About the person themselves
These land in the bell as well, and carrynotificationId.
security and orders pushes cannot be turned off in /v1/me/notification-preferences
(labels.{category}.lockedNote says why); saved_searches is set per search
(interval, push in /v1/me/saved-searches, and labels.saved_searches.managedPerSearch
is true); every other category can, per channel. Locked
on is our side only: a phone that refuses notifications still gets no push. Everything still lands in the bell.
On the phone, the order ones collapse per order, the saved-watch ones per watch, a saved
search’s per search and a thread’s messages per thread (apns-collapse-id / FCM tag): a
newer one replaces the older, so the lock screen shows where it ended up rather than every
step. The bell keeps every entry.
offer_accepted, offer_received and offer_refused may still be on older bell entries;
none of them is sent any more.
About a shop somebody works at
Push only, never the bell, so nonotificationId. They carry side: "seller" and
sellerId: switch into that shop before opening anything.
shop_unanswered follows the app plan’s rules: the last message in the thread is the
buyer’s, read or not; not an archived thread, not a watch that is sold, offline or
deleted, not a thread that led to an order, not a thread the shop marked noReplyNeeded
until the buyer writes again, not Wristler’s own aftercare thread, not a thread closed by a
block or with a buyer Wristler blocked; one reminder per waiting message, everything
waiting in one push, and at most one per shop per 2 hours. Only messages from the last
14 days count. Checked every 15 minutes.
A shop’s news
Somebody who runs a shop can be told when a buyer writes to it, makes it an offer, or pays for one of its watches. That is theshop category in the notification
preferences, and it is on unless turned off, like every other category: only
people who work at a shop ever receive it. PATCH categories.shop.push to false
for somebody who does not want it. Its mail is the digest of what buyers wrote and
the “your watch was sold” mail, and is switchable the same way.
These pushes carry data.type: "shop", data.side: "seller" and data.sellerId,
and either conversationId (a message or an offer, in the shop’s inbox) or orderId
(a paid order, in the shop’s orders) — see the table above. Switch into that shop before you open anything:
the thread or order belongs to the shop’s side, not to the person’s own. They do not
land in the bell, so there is no notificationId.