Skip to content

Instantly share code, notes, and snippets.

@tomredman
Created February 16, 2024 17:37
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save tomredman/e99cb764d2342ef68fc66e6c63763489 to your computer and use it in GitHub Desktop.
Save tomredman/e99cb764d2342ef68fc66e6c63763489 to your computer and use it in GitHub Desktop.
Accepting & verifying Facebook webhooks in NextJS
import crypto from "crypto";
import type { NextApiRequest, NextApiResponse } from "next";
import { buffer } from "micro";
export const config = {
api: {
bodyParser: false,
},
};
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<any>
) {
console.log("Webhook received!", req.method, req.query);
if (req.query?.["hub.mode"] === "subscribe") {
if (!verifyEndpointSubscription(req)) {
return res.status(403).send("Invalid verification token");
}
return res.send(req.query?.["hub.challenge"]);
}
const payload = await verifyPayload(req);
if (!payload) {
return res.status(403).send("Invalid signature");
}
if (payload.object === "instagram") {
payload.entry.forEach((entry: any) => {
entry.changes.forEach((change: any) => {
if (change.field === "story_insights") {
console.log("Story insights received!");
console.log(change.value);
/**
* Send tests from here: https://developers.facebook.com/apps/<your_app_id>/webhooks
*
{
"field": "story_insights",
"value": {
"media_id": "17887498072083520",
"impressions": 444,
"reach": 44,
"taps_forward": 4,
"taps_back": 3,
"exits": 3,
"replies": 0
}
}
*/
}
});
});
}
return res.status(200).send("OK");
}
const verifyPayload = async (req: NextApiRequest) => {
const rawBody = (await buffer(req)).toString();
const data = JSON.parse(rawBody);
const calculatedChecksum =
"sha256=" +
crypto
.createHmac("sha256", process.env.FB_APP_SECRET as string)
.update(rawBody)
.digest("hex");
const headerChecksum = req.headers["x-hub-signature-256"] as string;
const calculatedChecksumBuffer = Buffer.alloc(
calculatedChecksum.length,
calculatedChecksum
);
const headerChecksumBuffer = Buffer.alloc(
headerChecksum.length,
headerChecksum
);
if (crypto.timingSafeEqual(calculatedChecksumBuffer, headerChecksumBuffer)) {
return data;
}
return false;
};
const verifyEndpointSubscription = (req: NextApiRequest) => {
const token = process.env.SUBSCRIPTION_VERIFICATION_TOKEN;
return token === (req.query?.["hub.verify_token"] as string);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment