Fail to verify HMAC x-shopify-hmac-sha256 header in NESTJS

I have created a webhook in the Shopify Admin under notification settings that calls an endpoint to my BackEnd application. I am using NestJS and I’m attempted to verify the webhook using the provided HMAC x-shopify-hmac-sha256 header.

import {
  Injectable,
  NestMiddleware,
  UnauthorizedException,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Request, Response, NextFunction } from 'express';
import * as crypto from 'crypto';

@Injectable()
export class ShopifyWebhookMiddleware implements NestMiddleware {
  private readonly SHOPIFY_SECRET: string;

  constructor(private configService: ConfigService) {
    this.SHOPIFY_SECRET = this.configService.get<string>(
      'SHOPIFY_WEBHOOK_SECRET',
    );
  }

  use(req: Request, res: Response, next: NextFunction) {
    console.log('---------here----------------');
    const hmac = req.headers['x-shopify-hmac-sha256'] as string;
    if (!this.verifyShopifyWebhook(req.body, hmac)) {
      throw new UnauthorizedException('Invalid Shopify webhook signature');
    }
    next();
  }

  private verifyShopifyWebhook(body: unknown, hmac: string): boolean {
    if (!hmac || !this.SHOPIFY_SECRET) {
      return false;
    }

    const message = JSON.stringify(body);
    const generated_hash = crypto
      .createHmac('sha256', this.SHOPIFY_SECRET)
      .update(message, 'utf8')
      .digest('base64');

    console.log('generated_hash', generated_hash);
    console.log('hmac', hmac);
    return crypto.timingSafeEqual(
      Buffer.from(generated_hash, 'utf8'),
      Buffer.from(hmac, 'utf8'),
    );
  }
}

I am using the secret provided on the webhooks page in the Shopify Admin notification settings. No matter what I do, the values are not matching.
Anyone can help me? Thanks very much

Is body populated? If so, what format is it? JSON, string etc

This works, where the body is the raw content of the webhook.

const generatedHash = crypto
      .createHmac('sha256', secret)
      .update(body)
      .digest('base64');
 return generatedHash === hmac;
1 Like

@Minh_Le in my experience with Node.js applications, you’ll need to access the raw body, not the parsed one.

Are you sure that this req.body isn’t parsed into an object? I could be wrong but usually request’s also have a req.rawBody that is the raw string of the body before it’s interpreted.

Could you try passing req.rawBody instead?

thanks every one, i resolved it

1 Like