Middleware

In order to not expose your webhook endpoint to the public, you can verify the signature of the incoming payload and cross-reference it with your signing key you got from the dashboard. For details on HMAC security, review this Wikipedia page.

Make sure not to share your signing key with anyone. If you believe your signing key has been compromised, be sure to refresh it from the Penciled dashboard.

Example webhook endpoint

router.post('/webhook', securityMiddleware, async (req: Request, res: Response) => {
  console.log("/webhook req.body: ", req.body);
  // save recording, transcript, outcome, etc
})

Example middleware

export const securityMiddleware = async (req: Request, res: Response, next: NextFunction) => {
  try {
    const signingKey = process.env.PENCILED_SIGNING_KEY; // from the dashboard

    const signature = req.headers['x-penciled-signature'] as string;

    const verified: boolean = verifyWebhookSignature(signingKey, JSON.stringify(req.body), signature);

    if (!verified) {
      console.error('Webhook verification failed');
      return res.status(401).json({ error: 'Unauthorized' });
    }

    console.log('Webhook payload verified');

    next();

  } catch (error) {
    console.error('Error verifying webhook:', error);
    res.status(401).send('Unauthorized');
  }
};

Verify Webhook Signature

import crypto from 'crypto';

/**
 * Verifies the signature of a webhook payload using the provided key.
 * 
 * @param secretKey - The key on-hand to verify the signature. Located in environment variables
 * @param data - The body of the request
 * @param signature - The signature that came in the request headers
 * @returns A boolean indicating whether the signature is valid
 */
export function verifyWebhookSignature(secretKey: string, data: any, signature: string): boolean {
  const expectedSignature = crypto.createHmac('sha256', secretKey)
    .update(data)
    .digest('hex');

  return expectedSignature === signature;
}