messagebird-nodejs icon indicating copy to clipboard operation
messagebird-nodejs copied to clipboard

Webhook signature problems with Nextjs

Open achadee opened this issue 1 year ago • 1 comments

Hey guys, I can't seem to get the webhook verification working.

The docs seem to be out of date and the variables as I needed to manually construct the headers, currently getting "Signatures not match."

  const signature = headers().get("Messagebird-Signature");
  const timestamp = headers().get("Messagebird-Request-Timestamp");
  const body = await req.text();

  const result = validate(
    {
      headers: {
        "messagebird-request-timestamp": timestamp,
        "messagebird-signature": signature,
      },
      body,
      query: {
        statusDatetime: "2019-01-11T09:17:11+00:00",
      },
      url: `https://${process.env.NGROK_DEVELOPER_URL || process.env.NEXT_PUBLIC_WEB_URL}/api/webhooks/bird`,
    },
    process.env.BIRD_SHARED_SECRET || ""
  )

achadee avatar Aug 05 '24 05:08 achadee

Attaching more code here, to be clear, still need help - Can someone from the technical team please reach out!

following these docs: https://docs.bird.com/api/notifications-api/api-reference/webhooks/verifying-a-webhook

import { NextApiRequest, NextApiResponse } from 'next';
import crypto from 'crypto';
import { NextRequest } from 'next/server';
import { type IncomingHttpHeaders } from "http2";
import { headers } from 'next/headers'

export async function POST(
  req: NextRequest & {
    headers: IncomingHttpHeaders & {
      "Messagebird-Request-Timestamp": string;
      "Messagebird-Signature": string;
    };
  },
) {

  const headersList = headers()

  const signature = headersList.get('messagebird-signature')
  const timestamp = headersList.get('messagebird-request-timestamp')

  if (!signature || !timestamp) {
    return new Response(JSON.stringify({ message: 'Missing required headers' }), { status: 400 });
  }

  const body = await req.text();
  const url = req.url

  if (!verifyWebhook(signature, timestamp, body, url)) {
    return new Response(JSON.stringify({ message: 'Invalid signature' }), { status: 401 });
  }

  // Webhook is verified, process the payload
  const payload = JSON.parse(body.toString());
  console.log('Verified webhook payload:', payload);

  // Your webhook handling logic here

  return new Response(JSON.stringify({ message: 'Webhook received and verified' }), { status: 200 });
}

function verifyWebhook(signature: string, timestamp: string, body: string, url: string): boolean {

  // Base64 decode the messagebird-signature header return string;
  const decodedSignature = Buffer.from(signature, 'base64').toString('utf-8');

  // Create a SHA256 hash checksum of the request body as a binary result;
  const hash = crypto.createHash('sha256');
  const checksumBody = hash.update(body).digest("binary");

  const payload = [timestamp, url, checksumBody].join('\n')

  // Calculate HMACSHA256 using the signing key as the secret and the joined payload as the message;
  const hmac = crypto.createHmac('sha256', process.env.BIRD_SHARED_SECRET || "");
  const checksum = hmac.update(payload).digest('base64');

  // Compare the calculated checksum with the decoded signature;
  return checksum === decodedSignature;
}

achadee avatar Aug 30 '24 01:08 achadee