Webhook signature verification
Verify that a webhook request comes from Subrite by computing an HMAC SHA256 of the timestamp and payload with your webhook secret. Includes a NestJS example.
Upon receiving the request at your endpoint, it is recommended to verify the signature to ensure the request's authenticity and integrity. This can be done by comparing the x-subrite-webhook-signature header with a computed signature based on your webhook secret and the request payload. The x-subrite-webhook-signature-timestamp header can be used to prevent replay attacks by ensuring the request is within an acceptable time frame.
Verification steps#
Here is a basic outline of the steps for signature verification:
- Retrieve the signature and timestamp from the headers:
x-subrite-webhook-signaturex-subrite-webhook-signature-timestamp
- Create a signature string: Combine the timestamp and the request payload.
- Compute the HMAC: Use your webhook secret and the combined string to compute an HMAC SHA256 hash.
- Compare signatures: Compare the computed hash with the received signature.
By implementing these steps, you can ensure that the request is genuinely from Subrite and has not been tampered with during transmission.
NestJS example#
Below is a sample implementation in TypeScript for verifying the signature of incoming webhook requests in a NestJS application. This example demonstrates how to extract the signature and timestamp from the request headers, compute the HMAC signature, and compare it to the received signature.
import { Controller, Post, Body, Headers, HttpException, HttpStatus } from '@nestjs/common';
import * as crypto from 'crypto';
@Controller('webhook')
export class WebhookController {
@Post('test')
testWebhook(@Body() body: unknown, @Headers() headers: Record<string, string>) {
const signature = headers['x-subrite-webhook-signature'] as string;
const timestamp = headers['x-subrite-webhook-signature-timestamp'] as string;
if (!this.verifySignature(signature, timestamp, JSON.stringify(body))) {
throw new HttpException('Invalid signature', HttpStatus.UNAUTHORIZED);
}
// Implement your logic
}
verifySignature(signature: string, timestamp: string, body: string = '') {
const secret = 'your-webhook-secret';
const algorithm = 'sha256';
const computedSignature = this.computeSignature(secret, algorithm, timestamp, body);
return computedSignature === signature;
}
computeSignature(
secret: string,
algorithm: string,
timestamp: string,
body: string = '',
): string {
const data = body || '';
const hmac = crypto.createHmac(algorithm, secret);
const signature = hmac.update(timestamp + data).digest('base64');
return signature;
}
}