Reflects the switch from hard-coded FRONTEND_BASE_URL/utils.deep_links to admin-managed NotificationLink rows: implementation.md documents the resolution logic and the new migration, frontend-notification-click-actions.md replaces the "confirm your URL scheme" ask with the actual code catalog (now that the scheme itself is an admin panel concern, not something the frontend team needs to weigh in on for us to ship), and notification-click-actions.md gets a pointer at the top so it reads as the historical first pass it now is, not the current mechanism. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
6.7 KiB
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:
{
"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.
URLs are admin-configured, not hard-coded — nothing for you to build here
The actual destination string is no longer computed in backend code. Each
notification type has a code, and the notifications service resolves
that code against an admin-managed table (NotificationLink: code,
url_template, is_active) at send time. url_template supports a
{object_id} placeholder, e.g. https://app.gooyal.ir/ads/{object_id} or
gooyal://ads/{object_id} — whichever scheme your app actually uses.
This means: your routing scheme is a config decision, not a code change.
Whoever manages the notifications service's admin panel enters one row per
code below with your actual URL format, and every notification of that type
immediately starts carrying it. If a code has no row yet (or its row is
marked inactive), that notification simply has no click_url — same as any
other non-clickable notification, no error.
Practically, this doesn't change anything about what you build — you still
just read extras["client::notification"]["click"]["url"] and navigate if
it's present. It changes who's responsible for the URL string itself: not
a backend deploy, just an admin panel entry. If you want a different value
than what's currently configured for any code, ask whoever owns that panel
to update it.
What's clickable today, by code
All of these are live in the advertising service:
Billboards & content
| Code | Notification text (fa) |
|---|---|
billboard.viewed |
شخصی شروع به مشاهده بیلبورد شما کرد. |
billboard.created |
بیلبود شما با موفقیت ساخته شد. |
billboard.approved |
بیلبورد شما تایید شد. |
billboard.rejected |
بیلبود شما رد شد. |
billboard.commented |
یک نفر برای بیلبورد شما نظر گذاشت! |
billboard.replied |
یک نفر به نظر شما پاسخ داد! |
billboard.content_bought |
درآمد جدید دارید! یک نفر محتوای شما را خرید. |
billboard.content_supported |
یک نفر از محتوای شما حمایت کرد! |
billboard.pin_expiring |
زمان پین رو به اتمامه! |
billboard.pin_expired |
پین شما منقضی شد! |
billboard.credit_low |
اعتبار پین رو به اتمامه! |
billboard.credit_exhausted |
اعتبار پین شما به پایان رسید! |
Every code above sends the ad's uuid as click_object_id, so a
url_template of https://app.gooyal.ir/ads/{object_id} (or your app's
real equivalent) resolves correctly for all twelve. They're still separate
codes rather than one shared one, on purpose: billboard.pin_expiring and
billboard.credit_low are genuinely different situations (time running out
vs. budget running out) even though they'd point at the same screen today —
giving each its own code means that can diverge later (e.g. deep-linking
straight to an "extend time" vs. "top up credit" action) without any code
change, just a new admin row.
Escrow deals
| Code | Trigger |
|---|---|
escrow.request_deal |
Buyer paid, seller needs to review |
escrow.cancel_deal |
Buyer canceled before seller responded |
escrow.reject_deal |
Seller declined the deal |
escrow.approve_deal |
Seller confirmed the deal |
escrow.cancel_deal_by_seller |
Seller canceled an active deal |
escrow.request_cancel |
Buyer requested cancellation |
escrow.approve_cancel |
Seller accepted the cancellation |
escrow.reject_cancel |
Seller declined the cancellation |
escrow.confirm_by_seller |
Seller marked it delivered |
escrow.confirm_by_buyer |
Buyer confirmed receipt — deal completed |
escrow.request_judge |
A dispute was opened |
escrow.cancel_judge |
Buyer withdrew their dispute |
escrow.approve_judge |
Dispute resolved for the buyer |
escrow.reject_judge |
Dispute resolved for the seller |
escrow.timeout |
Deal expired without a response |
All fifteen send the escrow deal's uuid as click_object_id. Same
one-code-per-situation reasoning as billboards — they all currently resolve
to the same escrow-detail destination, but that's an admin config choice,
not a hard-coded one, so it's free to diverge later.
Not yet migrated to this mechanism
- chat: sends
extras = {"conversation_uuid": ..., "post_id": ...}directly, without a code orclick_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. Let us know if you'd rather chat send a code too. - promotions: integrated with the notifications service but not currently sending any notification with real content (
extras={}today) — nothing to build against yet.