Skip to main content

Overview

Webhook signature verification ensures that incoming webhook requests are genuinely from Fin and have not been tampered with. This process uses HMAC (Hash-based Message Authentication Code) to validate each webhook payload.

How It Works

  1. Extract the signature: Retrieve the signature value from the webhook request header
  2. Recalculate the signature: Use your secret key and the same hash algorithm to compute the HMAC signature for the received payload
  3. Compare signatures: Verify that your computed signature matches the one provided in the header

Verification Steps

Follow these steps to verify webhook signatures in your application:
  1. Read the raw request body - Capture the HTTP body as raw bytes without any modifications or parsing
  2. Extract signature headers - Read the x-fin-signature and x-fin-signature-algorithm headers from the request
  3. Compute the HMAC - Using your webhook secret key and the algorithm specified in x-fin-signature-algorithm, calculate:
    HMAC(algorithm, secretKey, rawBodyBytes) → hexadecimal string
    
  4. Compare signatures - Accept the webhook only if your computed signature exactly matches the x-fin-signature header value
Always use a constant-time comparison function to prevent timing attacks when comparing signatures.

Code Examples

Here are implementation examples in different programming languages:
const crypto = require('crypto');

function verifyWebhookSignature(request, webhookSecret) {
  // Extract headers
  const receivedSignature = request.headers['x-fin-signature'];
  const algorithm = request.headers['x-fin-signature-algorithm'] || 'sha256';
  
  // Get raw body as string or buffer
  const rawBody = request.body; // Ensure this is the raw body, not parsed JSON
  
  // Compute HMAC
  const computedSignature = crypto
    .createHmac(algorithm, webhookSecret)
    .update(rawBody, 'utf8')
    .digest('hex');
  
  // Compare signatures using constant-time comparison
  return crypto.timingSafeEqual(
    Buffer.from(receivedSignature),
    Buffer.from(computedSignature)
  );
}

// Usage example with Express.js
app.post('/webhooks', express.raw({ type: 'application/json' }), (req, res) => {
  const isValid = verifyWebhookSignature(req, process.env.WEBHOOK_SECRET);
  
  if (!isValid) {
    return res.status(401).send('Invalid signature');
  }
  
  // Process the webhook
  const payload = JSON.parse(req.body);
  console.log('Valid webhook received:', payload);
  res.status(200).send('OK');
});

Best Practices

Never hardcode your webhook secret in your source code. Use environment variables or a secure secrets management system.
Always use constant-time comparison functions (like crypto.timingSafeEqual in Node.js or hmac.compare_digest in Python) to prevent timing attacks.
Ensure your web framework provides access to the raw request body before any parsing or modifications occur.
Return appropriate HTTP status codes (401 Unauthorized) for failed verifications without revealing specific error details.

Troubleshooting

If signature verification is failing:
  • Check the raw body: Ensure you’re using the exact raw bytes received, not a re-serialized version
  • Verify the secret: Confirm you’re using the correct webhook secret from your Fin dashboard
  • Check character encoding: Make sure you’re using UTF-8 encoding consistently
  • Inspect headers: Verify that x-fin-signature and x-fin-signature-algorithm headers are present
  • Test the algorithm: Confirm you’re using the algorithm specified in the x-fin-signature-algorithm header