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
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": "eyJkb21haW4iOiJ0ZXN0In0",
"signature": "MHhlZWJkZjlhNDgwMWI2YTExNTc2MjdiZDg2OTM3YTAxN2ZlMzFhMzQ2Njk1YjMxZjFmZTFhOWYzZGUyNjY0MjEwNGJiMDRlMGJlNDdmZjg4YWE4NjIzOWUwZTg1MDc4MDQzNDdmNGUxNmUwMDAwOTUzY2VhMGQwMDdhNDEwMTBjZDFj"
},
"frame": {
"name": "Example Frame",
"version": "0.0.1",
"iconUrl": "https://example.com/icon.png",
"homeUrl": "https://example.com",
"splashImageUrl": "https://example.com/splash.png",
"splashBackgroundColor": "#eeccff",
"webhookUrl": "https://example.com/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 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 2 way to get these:
- The return value when calling
addFrame
(see below) - Via a webhook, so that you can get the
token
andurl
when the frame is added but not open, and when a user re-enables notification (see below)
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 result type is:
export type FrameNotificationDetails = {
url: string;
token: string;
};
export type AddFrameResult =
| {
added: true;
notificationDetails?: FrameNotificationDetails;
}
| {
added: false;
reason: 'invalid_domain_manifest' | 'rejected_by_user';
};
- 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.
Example code to process webhook events
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;
};