-
Notifications
You must be signed in to change notification settings - Fork 173
Pat/add notifs #436
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Pat/add notifs #436
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
15c773e
add context doc
hughescoin 350e2f3
Merge branch 'master' into pat/add-context
hughescoin 15883d5
update description and remove duplicate title
hughescoin 09e0bc2
add panel + streamline implementation steps
hughescoin 91cadae
remove extra paragraph
hughescoin 43d2046
update user object definition
hughescoin 06ed934
use cards and paramfields
hughescoin 4a66be0
first pass notifs
hughescoin d7f72ce
update notifications with common formatting
hughescoin 81fea56
Merge master into pat/add-notifs, using master version of context.mdx
hughescoin 33ce93a
use value focused description
hughescoin 0528078
add manifest webhook with highlighting
hughescoin ca2e2af
add code block for sending notification
hughescoin ccdc794
update title of notif
hughescoin 696ce44
add overview in beginning
hughescoin 91467bc
swap farcaster with Base app
hughescoin 1dfc983
add a callout for how notifications will function
hughescoin bd92281
reorganize code snippets and function names
hughescoin 2eafdb4
add image and change overview
hughescoin 19a1425
convert tables into Cards with ParamFields:
hughescoin 4bc4689
emphasize constraints
hughescoin 0d8e961
make code block expandable with highlights
hughescoin 80f49b2
add highlighting to addMiniApp
hughescoin bd247f0
change push to in-app
hughescoin File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,319 @@ | ||
--- | ||
title: Notifications (coming soon) | ||
description: Send relevant, rate‑limited notifications to users who saved your Mini App | ||
title: Notifications | ||
description: Regularly re-engage users by sending in-app notifications through the Base app | ||
--- | ||
|
||
<Info> | ||
Notifications are not yet available in Base App but are planned for a future release. | ||
</Info> | ||
## Overview | ||
|
||
Push notifications will allow you to re-engage users who have saved your Mini App, driving retention and bringing users back at key moments like new content releases, achievements, or time-sensitive events. | ||
When a user enables notifications for your Mini App, the Base App generates a unique notification `token` and `url` which is sent to your server via webhook. | ||
|
||
When available, notifications will be rate-limited and only sent to users who have explicitly saved your Mini App to their collection. | ||
This `token` grants your app permission to send in-app notifications to that specific user. | ||
|
||
To send a notification, make a `POST` request to the `url` with the user's notification `token` and your content. | ||
|
||
You will receive webhook events when users enable or disable notifications for your app. When disabled, the notification token becomes invalid and should no longer be used. | ||
<Panel> | ||
 | ||
|
||
</Panel> | ||
## Implementation | ||
<Steps> | ||
<Step title="Create a webhook server"> | ||
Create a webhook server to handle webhook events. | ||
|
||
```ts app/api/webhook/route.ts expandable highlight={20-50} | ||
export async function POST(request: NextRequest) { | ||
const requestJson = await request.json(); | ||
|
||
// Parse and verify the webhook event | ||
let data; | ||
try { | ||
data = await validateWebhookEventSignature(requestJson); | ||
// Events are signed by the app key of a user with a JSON Farcaster Signature. | ||
} catch (e: unknown) { | ||
// Handle verification errors (invalid data, invalid app key, etc.) | ||
// Return appropriate error responses with status codes 400, 401, or 500 | ||
} | ||
|
||
const fid = data.fid; | ||
const event = data.event; | ||
|
||
// Handle different event types | ||
switch (event.event) { | ||
case "miniapp_added": | ||
// Save notification details and send welcome notification | ||
if (event.notificationDetails) { | ||
await setUserNotificationDetails(fid, event.notificationDetails); | ||
await sendMiniAppNotification({ | ||
fid, | ||
title: "Welcome to Base Mini Apps", | ||
body: "Mini app is now added to your client", | ||
}); | ||
} | ||
break; | ||
|
||
case "miniapp_removed": | ||
// Delete notification details | ||
await deleteUserNotificationDetails(fid); | ||
break; | ||
|
||
case "notifications_enabled": | ||
// Save new notification details and send confirmation | ||
await setUserNotificationDetails(fid, event.notificationDetails); | ||
await sendMiniAppNotification({ | ||
fid, | ||
title: "Ding ding ding", | ||
body: "Notifications are now enabled", | ||
}); | ||
break; | ||
|
||
case "notifications_disabled": | ||
// Delete notification details | ||
await deleteUserNotificationDetails(fid); | ||
break; | ||
} | ||
|
||
return Response.json({ success: true }); | ||
} | ||
``` | ||
</Step> | ||
<Step title="Add the Webhook URL to your manifest"> | ||
Add the Webhook URL to your manifest file | ||
|
||
```json app/.well-known/farcaster.json highlight={16} | ||
hughescoin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
"accountAssociation": { | ||
"header": "eyJmaWQiOjU0NDgsInR5cGUiOiJjdXN0b2R5Iiwia2V5IjoiMHg2MWQwMEFENzYwNjhGOEQ0NzQwYzM1OEM4QzAzYUFFYjUxMGI1OTBEIn0", | ||
"payload": "eyJkb21haW4iOiJleGFtcGxlLmNvbSJ9", | ||
"signature": "MHg3NmRkOWVlMjE4OGEyMjliNzExZjUzOTkxYTc1NmEzMGZjNTA3NmE5OTU5OWJmOWFmYjYyMzAyZWQxMWQ2MWFmNTExYzlhYWVjNjQ3OWMzODcyMTI5MzA2YmJhYjdhMTE0MmRhMjA4MmNjNTM5MTJiY2MyMDRhMWFjZTY2NjE5OTFj" | ||
}, | ||
"miniapp": { | ||
"version": "1", | ||
"name": "Example App", | ||
"iconUrl": "https://example.com/icon.png", | ||
"homeUrl": "https://example.com", | ||
"imageUrl": "https://example.com/image.png", | ||
"buttonTitle": "Check this out", | ||
"splashImageUrl": "https://example.com/splash.png", | ||
"splashBackgroundColor": "#eeccff", | ||
"webhookUrl": "https://example.com/api/webhook" | ||
} | ||
} | ||
|
||
``` | ||
</Step> | ||
<Step title="Prompt users to add your Mini App"> | ||
Use the `addMiniApp()` hook to prompt users to add your Mini App | ||
hughescoin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
```tsx page.tsx highlight={11, 25-27} | ||
"use client"; | ||
|
||
import { sdk } from "@farcaster/miniapp-sdk"; | ||
import { useCallback, useState } from "react"; | ||
|
||
export default function AddMiniApp() { | ||
const [result, setResult] = useState(""); | ||
|
||
const handleAddMiniApp = useCallback(async () => { | ||
try { | ||
const response = await sdk.actions.addMiniApp(); | ||
|
||
if (response.notificationDetails) { | ||
setResult("Mini App added with notifications enabled!"); | ||
} else { | ||
setResult("Mini App added without notifications"); | ||
} | ||
} catch (error) { | ||
setResult(`Error: ${error}`); | ||
} | ||
}, []); | ||
|
||
return ( | ||
<div> | ||
<button onClick={handleAddMiniApp}> | ||
Add Mini App | ||
</button> | ||
{result && <p>{result}</p>} | ||
</div> | ||
); | ||
} | ||
``` | ||
|
||
</Step> | ||
<Step title="Save the token and URL from the webhook event"> | ||
The `token` and `url` need to be securely saved to a database so they can be looked up when you want to send a notification to a particular user. | ||
|
||
```json miniapp_added_payload.json highlight={4-5} | ||
{ | ||
"event": "notifications_enabled", | ||
"notificationDetails": { | ||
"url": "https://api.farcaster.xyz/v1/frame-notifications", | ||
"token": "a05059ef2415c67b08ecceb539201cbc6" | ||
} | ||
} | ||
``` | ||
|
||
|
||
</Step> | ||
<Step title="Send notifications"> | ||
Send notifications by sending a `POST` request to the `url` associated with the user's `token` | ||
hughescoin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
```ts sendNotification.ts highlight={15-28} | ||
export async function sendMiniAppNotification({ | ||
fid, | ||
title, | ||
body, | ||
}: { | ||
fid: number; | ||
title: string; | ||
body: string; | ||
}): Promise<sendMiniAppNotificationResult> { | ||
const notificationDetails = await getUserNotificationDetails(fid); | ||
if (!notificationDetails) { | ||
return { state: "no_token" }; | ||
} | ||
|
||
const response = await fetch(notificationDetails.url, { | ||
method: "POST", | ||
headers: { | ||
"Content-Type": "application/json", | ||
}, | ||
body: JSON.stringify({ | ||
notificationId: crypto.randomUUID(), | ||
title, | ||
body, | ||
targetUrl: appUrl, | ||
tokens: [notificationDetails.token], | ||
} satisfies SendNotificationRequest), | ||
}); | ||
|
||
const responseJson = await response.json(); | ||
|
||
if (response.status === 200) { | ||
const responseBody = sendNotificationResponseSchema.safeParse(responseJson); | ||
if (responseBody.success === false) { | ||
// Malformed response | ||
return { state: "error", error: responseBody.error.errors }; | ||
} | ||
|
||
if (responseBody.data.result.rateLimitedTokens.length) { | ||
// Rate limited | ||
return { state: "rate_limit" }; | ||
} | ||
|
||
return { state: "success" }; | ||
} else { | ||
// Error response | ||
return { state: "error", error: responseJson }; | ||
} | ||
} | ||
``` | ||
</Step> | ||
</Steps> | ||
|
||
## Schema | ||
|
||
### Send Notification Request Schema | ||
|
||
<Card> | ||
hughescoin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
<ParamField path="notificationId" type="string" required> | ||
Identifier that is combined with the FID to form an idempotency key. When the user opens the Mini App from the notification this ID will be included in the context object. **Maximum length of 128 characters.** | ||
</ParamField> | ||
|
||
<ParamField path="title" type="string" required> | ||
Title of the notification. **Max length 32 characters.** | ||
</ParamField> | ||
|
||
<ParamField path="body" type="string" required> | ||
Body of the notification. **Max length 128 characters.** | ||
</ParamField> | ||
|
||
<ParamField path="targetUrl" type="string" required> | ||
URL to open when the user clicks the notification. **Max length 1024 characters.** | ||
<Note>Must be on the same domain as the Mini App.</Note> | ||
|
||
</ParamField> | ||
|
||
<ParamField path="tokens" type="string[]" required> | ||
Array of notification tokens to send to. **Max 100 tokens.** | ||
</ParamField> | ||
</Card> | ||
|
||
### Send Notification Response Schema | ||
|
||
<Card> | ||
<ParamField path="successfulTokens" type="string[]" required> | ||
Tokens for which the notification succeeded. | ||
</ParamField> | ||
|
||
<ParamField path="invalidTokens" type="string[]" required> | ||
Tokens which are no longer valid and should never be used again. This could happen if the user disabled notifications but for some reason the Mini App server has no record of it. | ||
</ParamField> | ||
|
||
<ParamField path="rateLimitedTokens" type="string[]" required> | ||
Tokens for which the rate limit was exceeded. The Mini App server can try later. | ||
</ParamField> | ||
</Card> | ||
|
||
|
||
|
||
## Events | ||
|
||
Mini App events use the following object structure: | ||
|
||
* **`type`**: notification event type | ||
* **`notificationDetails.url`**: URL that the app should call to send a notification. | ||
* **`notificationDetails.token`**: A secret token generated by the Base App and shared with the Notification Server. A token is unique for each (Farcaster Client, Mini App, user Fid) tuple. | ||
|
||
<Note>If users are not seeing the option to enable notifications when they call `addMiniApp()`, verify that your manifest file contains a valid `webhookUrl`.</Note> | ||
|
||
### `miniapp_added` | ||
|
||
Sent when the user adds the Mini App to their Farcaster client (whether or not it was triggered by an `addMiniApp()` prompt). | ||
|
||
|
||
|
||
```json miniapp_added_payload.json | ||
{ | ||
"event": "miniapp_added", | ||
"notificationDetails": { | ||
"url": "https://api.farcaster.xyz/v1/frame-notifications", | ||
hughescoin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"token": "a05059ef2415c67b08ecceb539201cbc6" | ||
} | ||
} | ||
``` | ||
|
||
### `miniapp_removed` | ||
|
||
Sent when a user removes the Mini App, which means that any notification tokens for that FID and client app (based on signer requester) should be considered invalid: | ||
|
||
|
||
```json miniapp_removed_payload | ||
{ | ||
"event": "miniapp_removed" | ||
} | ||
``` | ||
|
||
### `notifications_enabled` | ||
|
||
Sent when a user enables notifications (e.g. after disabling them). The payload includes a new `token` and `url`: | ||
|
||
```json notifications_enabled_payload | ||
{ | ||
"event": "notifications_enabled", | ||
"notificationDetails": { | ||
"url": "https://api.farcaster.xyz/v1/frame-notifications", | ||
"token": "a05059ef2415c67b08ecceb539201cbc6" | ||
} | ||
} | ||
``` | ||
|
||
### `notifications_disabled` | ||
|
||
Sent when a user disables notifications from, e.g., a settings panel in the client app. Any notification tokens for that FID and client app (based on signer requester) should be considered invalid: | ||
|
||
```json notifications_disabled_json | ||
{ | ||
"event": "notifications_disabled" | ||
} | ||
``` |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.