Frame validation

Validate incoming frame actions to get genuine data

Frames are mini-apps inside Farcaster casts. To read more about frames, check out the Frames documentation.

Frame actions are POST request sent to a Frame server, and is unauthenticated by default. Checking the POST request payload against the Hub is a necessary step to ensure the request is valid.

This guide demonstrates how to verify a frame action payload against the Hub with Neynar SDK.

Check out this Getting started guide to learn how to set up your environment and get an API key.

First, initialize the client:

// npm i @neynar/nodejs-sdk
import { NeynarAPIClient, FeedType, FilterType } from "@neynar/nodejs-sdk";

// make sure to set your NEYNAR_API_KEY .env
// don't have an API key yet? get one at neynar.com
const client = new NeynarAPIClient(process.env.NEYNAR_API_KEY);

Frame server hosts frames and handles Frame actions. Here's what POST request payload looks like for a Frame action:

const payload = {
  untrustedData: {
    fid: 4286,
    url: "https://frame-server-example.com",
    messageHash: "0x8e95825cca8e81db6b9bd64bfdf626f7f172f02e",
    timestamp: 1707640145000,
    network: 1,
    buttonIndex: 1,
    castId: {
      fid: 4286,
      hash: "0x0000000000000000000000000000000000000001",
    },
  },
  trustedData: {
    messageBytes:
      "0a60080d10be2118d1bee82e20018201510a3268747470733a2f2f726e766d622d3130332d3132312d3133382d3131332e612e667265652e70696e6767792e6f6e6c696e6510011a1908be211214000000000000000000000000000000000000000112148e95825cca8e81db6b9bd64bfdf626f7f172f02e1801224096dd456e2752a358b33e19cfa833478974ce53379939e721e2875df14df4237a3ad3c9c9a27768ab59ea360df34893111c9aadc819a972d8ffd5f66976ffc00328013220836bf050647d18d304124823aaefa7c82eef99cbab2a120d8a8fe8e6d391929d",
  },
};

Anyone can spoof the request and send it to the Frame server. The check whether it's a valid request (ie. fid 4286 pressing buttonIndex 1 on a specific cast), we need to verify the request against the Hub.

const result = await client.validateFrameAction(payload.trustedData.messageBytes);
// console.log(result);
{
  "valid": true,
  "action": {
    "object": "validated_frame_action",
    "interactor": {
      "object": "user",
      "fid": 4286,
      "custody_address": "0x0076f74cc966fdd705ded40df8ab86604e4b5759",
      "username": "pixel",
      "display_name": "vincent",
      "pfp_url": "https://lh3.googleusercontent.com/WuVUEzf_r3qgz3cf4mtkXpLat5zNZbxKjoV-AldwfCQ8-_Y5yfWScMBEalpvbVgpt4ttXruxTD9GM983-UJBzMil5GRQF1qZ_aMY",
      "profile": {},
      "follower_count": 34997,
      "following_count": 905,
      "verifications": [
        "0x0076f74cc966fdd705ded40df8ab86604e4b5759",
        "0xb7254ce5cb61f69b3fc120b85f0f6b90d871036c"
      ],
      "verified_addresses": {
        "eth_addresses": [
        	"0x0076f74cc966fdd705ded40df8ab86604e4b5759",
	        "0xb7254ce5cb61f69b3fc120b85f0f6b90d871036c"
        ],
      	"sol_addresses": [
          "7rhxnLV8C77o6d8oz26AgK8x8m5ePsdeRawjqvojbjnQ",
          "8g4Z9d6PqGkgH31tMW6FwxGhwYJrXpxZHQrkikpLJKrG"
        ],
      },
      "active_status": "active"
    },
    "tapped_button": {
      "index": 1
    },
    "input": {
      "text": ""
    },
    "url": "https://frame-server-example.com",
    "cast": {
      "object": "cast_dehydrated",
      "hash": "0x0000000000000000000000000000000000000001",
      "fid": 4286
    },
    "timestamp": "2024-02-11T08:29:05.000Z"
  },
  "signature_temporary_object": {
    "note": "temporary object for signature validation, might be removed in future versions. do not depend on this object, reach out if needed.",
    "hash": "0x8e95825cca8e81db6b9bd64bfdf626f7f172f02e",
    "hash_scheme": "HASH_SCHEME_BLAKE3",
    "signature": "lt1FbidSo1izPhnPqDNHiXTOUzeZOech4odd8U30I3o608nJondoq1nqNg3zSJMRHJqtyBmpctj/1fZpdv/AAw==",
    "signature_scheme": "SIGNATURE_SCHEME_ED25519",
    "signer": "0x836bf050647d18d304124823aaefa7c82eef99cbab2a120d8a8fe8e6d391929d"
  }
}

And that's it, a valid Frame action! You've successfully verified a frame action payload against the Hub with Neynar SDK. Note that frame payloads are only available in responses, not in initial requests. Attempts to fetch a payload during a request will result in an error. If you want to fetch cast while doing frame validation, refer to our How to get cast information from Warpcast URL guide.

🚀

Ready to start building?

Get your subscription at neynar.com and reach out to us on Telegram with any questions!