notifications/docs/frontend-notification-click-actions.md
Ali Asadi 354bbc26cf docs(notifications): add service overview, implementation guide, and frontend handoff doc
Three docs for three audiences: service-overview.md for anyone integrating
with or operating the service, implementation.md for engineers maintaining
this codebase, and frontend-notification-click-actions.md as a self-contained
handoff for the client team to start building tap-to-navigate against — it
flags the click_url URL format as an unconfirmed placeholder needing their
input, and catalogs every notification type currently sending one.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-22 11:39:30 +03:30

117 lines
6.1 KiB
Markdown

# Frontend integration guide: notification click actions
**Audience**: the team building the Gooyal client app(s) that receive Gotify
push notifications. This is everything you need to start building tap-to-navigate,
independent of the backend repos.
## The one thing to build
When a push notification is tapped, read a URL out of the notification's
payload and navigate to it. That's the entire client-side contract — one
handler, used by every notification type below, present or future.
## Where the URL lives
Gotify delivers a JSON payload per message. The URL — when the notification
is clickable at all — is under a reserved key, per
[Gotify's own convention](https://gotify.net/docs/pushmsg#extras):
```json
{
"title": "بیلبورد شما تایید شد.",
"message": "بیلبورد گربه (تست) تایید شد.",
"priority": 5,
"extras": {
"ads::ad::approve": true,
"ad_uuid": "8fb638c2-e6a4-4baf-aefe-83dab78fb5bd",
"client::notification": {
"click": { "url": "https://app.gooyal.ir/ads/8fb638c2-e6a4-4baf-aefe-83dab78fb5bd" }
}
}
}
```
Pull `extras["client::notification"]["click"]["url"]`. If it's present, tapping
navigates there. **If it's absent, do nothing on tap** — see next section.
## `click_url` is optional — most notifications are not clickable
Do not assume every notification carries a URL. The backend field this comes
from is explicitly optional (nullable end-to-end, not just "sometimes empty
string") specifically because not every notification type has a sensible
destination. Any notification without a `client::notification.click.url` key
should render and behave exactly as a non-interactive notification — no
error, no dead tap target, just no navigation.
You may also see other keys under `extras` alongside (or instead of)
`client::notification` — e.g. chat currently sends `conversation_uuid` and
`post_id` directly, not yet through this mechanism (see "Not yet migrated"
below). Ignore keys you don't recognize; don't treat an unfamiliar key as an
error.
## ⚠️ The URL format below is a placeholder — needs your input
Every `click_url` currently being sent is built from a setting called
`FRONTEND_BASE_URL`, defaulting to `https://app.gooyal.ir`, with a path like
`/ads/<uuid>` or `/escrow/<uuid>` appended. **Nobody has confirmed this
matches your actual app's routing** — whether you use a custom URI scheme
(`gooyal://ads/<uuid>`), a universal/app link (`https://...`), or something
else entirely (a route name + params instead of a URL at all).
This was built centrally on purpose so it's a one-line change once you tell
us: every `click_url` in the system is built by one helper function per
producer service (`utils/deep_links.py` in `advertising`), not scattered
across dozens of call sites. **Please confirm your scheme and we'll update
it** — nothing about the client-side contract above changes either way,
only the string inside `click.url`.
## What's clickable today
All of the following are live in the `advertising` service and carry a real
`click_url` pointing at the ad (`/ads/<uuid>`) or escrow deal (`/escrow/<uuid>`):
### Billboards & content (→ `/ads/<uuid>`)
| Event | Notification text (fa) |
|---|---|
| Someone viewed your billboard | شخصی شروع به مشاهده بیلبورد شما کرد. |
| Billboard created | بیلبود شما با موفقیت ساخته شد. |
| Billboard approved | بیلبورد شما تایید شد. |
| Billboard rejected | بیلبود شما رد شد. |
| Someone commented on your billboard | یک نفر برای بیلبورد شما نظر گذاشت! |
| Someone replied to your comment | یک نفر به نظر شما پاسخ داد! |
| Someone bought your content | درآمد جدید دارید! یک نفر محتوای شما را خرید. |
| Someone supported your content | یک نفر از محتوای شما حمایت کرد! |
| Pin about to expire (time) | زمان پین رو به اتمامه! |
| Pin expired (time) | پین شما منقضی شد! |
| Pin credit about to run out (budget) | اعتبار پین رو به اتمامه! |
| Pin credit exhausted (budget) | اعتبار پین شما به پایان رسید! |
Note the last four are **two independent signals** — a billboard can lapse
because its display *time* ran out, or because its *budget* (balance vs.
reward-per-view) ran out. Both currently point at the same ad detail page;
if your UI wants to show a different call-to-action (extend time vs. top up
credit) based on which one fired, that distinction is in the `extras` payload
via which notification title/text arrived, not in the URL itself — ask if
you need a structured signal here instead of parsing title text.
### Escrow deals (→ `/escrow/<uuid>`)
The full P2P deal lifecycle — buyer pays, seller approves/rejects, delivery,
confirm, dispute, payout/refund. All 11 possible state transitions notify
whichever party (buyer or seller) needs to act or be informed next, each
with a `click_url` to that deal's detail page. If you're building an escrow
detail screen, treat every push in this domain as "go look at this deal" —
the screen itself should reflect current state, not the specific notification
that triggered the tap.
## Not yet migrated to this mechanism
- **chat**: sends `extras = {"conversation_uuid": ..., "post_id": ...}` directly, without a `click_url`. If your client already has custom handling for these two keys (built before this mechanism existed), it keeps working — this doc doesn't change that. If you'd rather chat also send a `click_url` once your routing scheme is confirmed, that's a small change on our side; let us know.
- **promotions**: integrated with the notifications service but not currently sending any notification with real content (`extras={}` today) — nothing to build against yet.
## Questions for you before we finalize
1. What's your app's actual deep-link scheme — custom URI, universal link, or something else?
2. Do you want the "time expiring" vs. "credit expiring" pin notifications distinguished by something other than title text?
3. Do you want chat migrated onto `click_url` too, or is your existing `conversation_uuid`/`post_id` handling staying as-is?