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>
117 lines
6.1 KiB
Markdown
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?
|