Frames v2 Notifications & Webhooks
Frames v2 allow developers to send notifications to users who have "added" the frame to their Farcaster client and enabled notifications.
In Warpcast, these are in-app notifications that trigger the red dot on the notifications tab. At this stage there is no support for push notifications.
The steps to successfully send a notification are:
- Set up a valid domain manifest
- Have a user add the frame to their Farcaster client. You can trigger a prompt via the
addFrame
action - Receive a notification
url
andtoken
and save these to persistent storage - Send a notification by POSTing to the
url
using thetoken
- Listen for webhooks that tell you when a user adds/removes the frame and enables/disables notifications
The Frames V2 Demo frame has all of the above:
- Handles webhooks leveraging the
@farcaster/frame-node
library that makes this very easy - Saves notification tokens to Redis
- Sends notifications
Create a Farcaster Domain Manifest
A Farcaster domain manifest is required for a frame to be eligible to be added to Farcaster clients and send notifications. It looks like this:
{
"accountAssociation": {
"header": "eyJmaWQiOjU0NDgsInR5cGUiOiJjdXN0b2R5Iiwia2V5IjoiMHg2MWQwMEFENzYwNjhGOEQ0NzQwYzM1OEM4QzAzYUFFYjUxMGI1OTBEIn0",
"payload": "eyJkb21haW4iOiJleGFtcGxlLmNvbSJ9",
"signature": "MHg3NmRkOWVlMjE4OGEyMjliNzExZjUzOTkxYTc1NmEzMGZjNTA3NmE5OTU5OWJmOWFmYjYyMzAyZWQxMWQ2MWFmNTExYzlhYWVjNjQ3OWMzODcyMTI5MzA2YmJhYjdhMTE0MmRhMjA4MmNjNTM5MTJiY2MyMDRhMWFjZTY2NjE5OTFj"
},
"frame": {
"version": "1",
"name": "Example Frame",
"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"
}
}
For a real example, this is Yoink's manifest: https://yoink.party/.well-known/farcaster.json
To generate and validate a domain manifest:
Open the Warpcast mobile app and ensure you have developer mode enabled in Settings > Advanced > Developer Mode
Go to Settings > Developer > Domains, enter your domain name, and press "Generate domain manifest". Make sure that "Include frame definition" is checked. This creates a signature associating the domain with your Farcaster account and copies the manifest JSON.
Complete the frame details in the JSON and validate it by pasting it in the "Verify Domain Manifest" section. The
webhookUrl
is an endpoint on your side that Farcaster clients will use to POST events. It is required for your app to be able to send notifications.Host the manifest at path
/.well-known/farcaster.json
on your server. Sub-paths are not supported: the.well-known
directory needs to be at the root of the domain.Once the manifest is live on your domain, use the "Check domain status" button in the domain developer tools to validate the manifest from your server.
Have users add your frame to their Farcaster client
For a frame to send notifications, it needs to first be added by a user to their Farcaster client, and then notifications need to be enabled. Warpcast always enables notifications when a frame is added (and the manifest contains a webhookUrl
) so a 2nd step is not needed.
When notifications are enabled, the Farcaster client generates a unique token
for the user. This token is communicated to the frame together with a url
that the frame must call. There are 3 ways to get these:
- The return value when calling
addFrame
(see below) - Via a webhook, so that you can get the
token
andurl
also when the frame is not open, and when a user re-enables notification (see below) - Via a frame event, when the user adds the frame while the frame is open
The token
and url
need to be saved to persistent storage.
Prompt users to add your frame to their Farcaster client
You can use the addFrame
action while a user is using your frame to prompt them to add it to their Farcaster client.
The return type is:
export type FrameNotificationDetails = {
url: string;
token: string;
};
export type AddFrameRejectedReason =
| 'invalid_domain_manifest'
| 'rejected_by_user';
export type AddFrameResult =
| {
added: true;
notificationDetails?: FrameNotificationDetails;
}
| {
added: false;
reason: AddFrameRejectedReason;
};
- When
added: true
is returned, the user just added your frame (approved) or the frame was already added before (user was not prompted). The optionalnotificationDetails
object provides thetoken
andurl
. Warpcast always provides these when a user first adds a frame, and on subsequent calls provided that the user has not disabled notifications. If you did not getnotificationDetails
when adding an app, ensure your domain manifest has awebhookUrl
. - When
added: false
is returned, the user either rejected the request or your domain manifest is invalid. You can only prompt a user once while a frame is open. SubsequentaddFrame
calls will directly resolve withreason: "rejected_by_user"
without prompting the user.
Send notifications
Once you have a url
and token
, you can send notifications by POSTing to the url
. Note that you have to do separate POSTs for each Farcaster client (= unique url
).
Example code to send a notification
Here are the types:
export const sendNotificationRequestSchema = z.object({
notificationId: z.string().max(128),
title: z.string().max(32),
body: z.string().max(128),
targetUrl: z.string().max(256),
tokens: z.string().array().max(100),
});
export type SendNotificationRequest = z.infer<
typeof sendNotificationRequestSchema
>;
export const sendNotificationResponseSchema = z.object({
result: z.object({
successfulTokens: z.array(z.string()),
invalidTokens: z.array(z.string()),
rateLimitedTokens: z.array(z.string()),
}),
});
export type SendNotificationResponse = z.infer<
typeof sendNotificationResponseSchema
>;
The request is a JSON consisting of:
notificationId
: a string (max size 128) that servers as an idempotency key and will be passed back to the frame via context. A Farcaster client should deliver only one notification per user pernotificationId
, even if called multiple times.title
: title of the notification, max 32 charactersbody
: body of the notification, max 128 characterstargetUrl
: the target frame URL to open when a user clicks the notification. It must match the domain for which the notification token was issued. Max 256 characters.tokens
: an array of tokens (for thaturl
) to send the notification to. Max 100 per call.
Note that client servers may impose rate limits per token
.
The response from the client server must be an HTTP 200 OK, with a result
object that contains 3 arrays:
successfulTokens
: tokens for which the notification succeeded, including those for which the request is a duplicate (samenotificationId
used before)invalidTokens
: tokens which are no longer valid and should never be used again. This could happen if the user disabled notifications.rateLimitedTokens
: tokens for which the rate limit was exceeded. Frame server can try later.
Once a user clicks the notification, the Farcaster client will:
- Open
targetUrl
- Set the
context.location
to aFrameLocationNotificationContext
export type FrameLocationNotificationContext = {
type: 'notification';
notification: {
notificationId: string;
title: string;
body: string;
};
};
Listen for webhooks to get updates
Farcast clients will POST events to your webhookUrl
informing you when a user:
- Adds the frame to the client (
frame_added
) - Removes the frame from the client which disables notifications (
frame_removed
) - Enabled notifications (
notifications_enabled
) - Disables notifications (
notifications_disabled
)
Your endpoint should return a 200 response. It is up to Farcaster clients how often and for how long they retry in case of errors.
Events use the JSON Farcaster Signature format, signed with the app key of the user. The data you'll receive is:
{
header: string;
payload: string;
signature: string;
}
All 3 values are base64url
encoded. The payload and header can be decoded to JSON.
The header
JSON has 3 properties:
fid
: the FID of the user triggering the eventtype
: the type of signature, alwaysapp_key
key
: the app key (onchain signer) public key
The payload
JSON differs per event, see below.
The @farcaster/frame-node
library makes handling very easy. The only thing it requires is a method that validates that the app key belongs to the FID and returns the Farcaster client FID. An implementation that uses Neynar is provided. Check out the README and see how it's used in the Frames V2 Demo frame.
frame_added
: frame added to a client
Sent when the user adds the frame to their Farcaster client (whether or not this was triggered by an addFrame()
prompt).
The optional notificationDetails
object provides the token
and url
if the client equates adding to enabling notifications (Warpcast does this).
Webhook payload:
{
"event": "frame_added",
"notificationDetails": {
"url": "https://api.warpcast.com/v1/frame-notifications",
"token": "a05059ef2415c67b08ecceb539201cbc6"
}
}
type EventFrameAddedPayload = {
event: 'frame_added';
notificationDetails?: FrameNotificationDetails;
};
frame_removed
: user removed frame from client
Sent when a user removes a frame, which means that any notification tokens for that fid and client app (based on signer requester) should be considered invalid:
Webhook payload:
{
"event": "frame_removed"
}
notifications_disabled
: user disabled notifications
Sent when a disables frame 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:
Webhook payload:
{
"event": "notifications_disabled"
}
notifications_enabled
: user enabled notifications
Sent when a user enables frame notifications (e.g. after disabling them, or if this is a separate step from adding a frame to the client). The payload includes a new token
and url
:
Webhook payload:
{
"event": "notifications_enabled",
"notificationDetails": {
"url": "https://api.warpcast.com/v1/frame-notifications",
"token": "a05059ef2415c67b08ecceb539201cbc6"
}
}
type EventNotificationsEnabledPayload = {
event: 'notifications_enabled';
notificationDetails: FrameNotificationDetails;
};
Listen for frame events to get updates while the frame is open
Farcaster clients emit events to your frame, while it is open, to let you know of actions the user takes.
To listen to events, you have to use sdk.on
to register callbacks (see full example).
sdk.on('frameAdded', ({ notificationDetails }) => {
setLastEvent(
`frameAdded${!!notificationDetails ? ', notifications enabled' : ''}`
);
setAdded(true);
if (notificationDetails) {
setNotificationDetails(notificationDetails);
}
});
Ensure that on unmount/close, all the listeners are removed via sdk.removeAllListeners()
.
Here are the callback definitions:
export type EventMap = {
frameAdded: ({
notificationDetails,
}: {
notificationDetails?: FrameNotificationDetails;
}) => void;
frameAddRejected: ({ reason }: { reason: AddFrameRejectedReason }) => void;
frameRemoved: () => void;
notificationsEnabled: ({
notificationDetails,
}: {
notificationDetails: FrameNotificationDetails;
}) => void;
notificationsDisabled: () => void;
};
The emitted events are:
frameAdded
, same as theframe_added
webhookframeAddRejected
, frontend-only, emitted when the frame has triggered theaddFrame
action and the frame was not added. Reason is the same as in the return value ofaddFrame
.frameRemoved
, same as theframe_removed
webhooknotificationsEnabled
, same as thenotifications_enabled
webhooknotificationsDisabled
, same as thenotifications_disabled
webhook