Skip to main content
Didit Raises $7.5M to Build the Infrastructure for Identity and Fraud
Didit
Back to blog
Blog · March 6, 2026

Secure Your Node.js Backend: HMAC Validation for Didit Webhooks

Implementing robust security measures for webhooks is crucial for data integrity and preventing unauthorized access. This guide focuses on HMAC signature validation for Didit webhooks in a Node.

By DiditUpdated
secure-your-nodejs-backend-hmac-validation-for-didit-webhooks.png

Protect Data IntegrityHMAC signature validation is essential for verifying the authenticity and integrity of webhook payloads, safeguarding your backend from tampered or fraudulent requests.

Prevent Replay AttacksTimestamp validation, in conjunction with HMAC signatures, adds a crucial layer of defense against replay attacks, ensuring that only fresh notifications are processed.

Seamless IntegrationDidit's developer-first approach provides clear documentation and tools, making the integration of secure webhook processing into your Node.js application straightforward and efficient.

Didit's Secure InfrastructureDidit's platform is built with security in mind, offering real-time KYC notifications via webhooks with robust HMAC signature verification, enabling you to build trusted, compliant identity solutions.

The Critical Need for Webhook Security

Webhooks are powerful tools for real-time communication between services, enabling instant updates and event-driven architectures. In the context of identity verification, Didit uses webhooks to notify your backend about the completion of verification sessions, AML screening results, or other critical identity-related events. While incredibly efficient, webhooks also introduce security challenges. Without proper validation, a malicious actor could send forged or altered webhook payloads, leading to incorrect data processing, unauthorized actions, or even system compromise.

This is where HMAC (Hash-based Message Authentication Code) signature validation becomes indispensable. HMAC provides a cryptographic mechanism to verify both the authenticity and integrity of a message. By using a shared secret key, your backend can ensure that the webhook payload originated from Didit and has not been tampered with in transit. For any system dealing with sensitive identity data, like that processed by Didit's ID Verification, AML Screening, or Proof of Address services, this level of security is not just a best practice—it's a necessity.

Understanding HMAC Signature Validation

HMAC validation works by generating a unique signature for each webhook payload using a secret key known only to Didit and your application. When your Node.js backend receives a webhook, it performs the same signature calculation using its copy of the secret key. If the calculated signature matches the signature provided in the webhook's headers, you can be confident that the payload is authentic and untampered.

Didit's webhooks include an X-Signature header containing the HMAC-SHA256 signature and an X-Timestamp header. The timestamp is crucial for preventing replay attacks. A replay attack occurs when an attacker intercepts a legitimate webhook and re-sends it later. By validating that the timestamp is fresh (e.g., within a 5-minute window), you can discard old, potentially replayed requests.

Implementing HMAC Validation in Node.js

Let's walk through the steps to implement HMAC signature validation for Didit webhooks in a Node.js Express application. You'll need access to your webhook secret key, which can be found in your Didit Business Console or retrieved via the management API.

1. Configure Your Webhook Endpoint

First, set up an Express endpoint that can receive POST requests. It's crucial to access the raw request body before any JSON parsing middleware, as the signature is calculated over the raw payload.


const express = require('express');
const crypto = require('crypto');
const bodyParser = require('body-parser');

const app = express();
const DIDIT_WEBHOOK_SECRET = process.env.DIDIT_WEBHOOK_SECRET; // Store securely!

// Middleware to get raw body
app.use(bodyParser.json({ verify: (req, res, buf) => {
  req.rawBody = buf.toString();
}}));

app.post('/api/webhooks/didit', (req, res) => {
  // Webhook processing logic will go here
  res.status(200).send('Webhook received');
});

app.listen(3000, () => console.log('Server running on port 3000'));

2. Verify the HMAC Signature

Inside your webhook handler, you'll extract the timestamp and signature from the headers, reconstruct the signed payload, and compare your calculated signature with the one provided by Didit.


app.post('/api/webhooks/didit', (req, res) => {
  const signatureHeader = req.headers['x-signature'];
  const timestampHeader = req.headers['x-timestamp'];
  const rawBody = req.rawBody;

  if (!signatureHeader || !timestampHeader || !rawBody) {
    return res.status(400).send('Missing webhook headers or body.');
  }

  const [algorithm, diditSignature] = signatureHeader.split('=');

  if (algorithm !== 'sha256') {
    return res.status(400).send('Unsupported signature algorithm.');
  }

  // Reconstruct the signed payload string
  const signedPayload = `${timestampHeader}.${rawBody}`;

  // Calculate your own signature
  const expectedSignature = crypto
    .createHmac('sha256', DIDIT_WEBHOOK_SECRET)
    .update(signedPayload)
    .digest('hex');

  // Compare signatures securely
  const signatureMatches = crypto.timingSafeEqual(
    Buffer.from(diditSignature, 'utf8'),
    Buffer.from(expectedSignature, 'utf8')
  );

  if (!signatureMatches) {
    console.warn('Webhook signature mismatch!');
    return res.status(401).send('Invalid signature.');
  }

  // Signature is valid, now check timestamp
  // ... (timestamp validation in next step)

  // Process event
  console.log('Webhook validated successfully:', req.body);
  res.status(200).send('Webhook received and processed.');
});

3. Validate the Timestamp

After verifying the signature, ensure the webhook is not a replay. A common practice is to allow a small tolerance window (e.g., 5 minutes) for network delays.


// ... (inside the webhook handler, after signature validation)

  const fiveMinutesAgo = Date.now() / 1000 - (5 * 60); // 5 minutes in seconds
  const eventTimestamp = parseInt(timestampHeader, 10);

  if (isNaN(eventTimestamp) || eventTimestamp < fiveMinutesAgo) {
    console.warn('Webhook timestamp too old or invalid!');
    return res.status(401).send('Invalid or old timestamp.');
  }

  // Now you can safely parse and process the JSON body
  try {
    const event = JSON.parse(rawBody);
    console.log('Processed Didit event:', event);
    // Example: Update user status based on event.database_validation.status

    // Didit's Database Validation Report structure:
    // const validationStatus = event.database_validation.status;
    // const matchType = event.database_validation.match_type;
    // console.log(`Validation Status: ${validationStatus}, Match Type: ${matchType}`);

    res.status(200).send('Webhook received and processed.');
  } catch (error) {
    console.error('Error parsing webhook body:', error);
    res.status(400).send('Invalid JSON body.');
  }
});

Best Practices for Webhook Handling

  • Store Secrets Securely: Never hardcode your webhook secret key. Use environment variables or a secure secret management service.
  • Idempotency: Design your webhook handlers to be idempotent. This means that processing the same webhook multiple times (due to retries, for example) should have the same effect as processing it once.
  • Asynchronous Processing: For long-running tasks, acknowledge the webhook immediately with a 200 OK response and then process the payload asynchronously using a message queue. This prevents timeouts and ensures Didit doesn't keep retrying the webhook.
  • Logging and Monitoring: Implement robust logging for all received webhooks, including validation failures. Monitor your webhook endpoint for unusual activity or high error rates.
  • Error Handling: Return appropriate HTTP status codes (e.g., 400 for bad request, 401 for unauthorized, 500 for server errors) to help Didit understand if a retry is necessary.

How Didit Helps

Didit is engineered to be an AI-native, developer-first identity platform, making secure integrations like webhook processing straightforward and reliable. Our modular architecture means you can easily plug in identity checks, including ID Verification, Passive & Active Liveness, and AML Screening & Monitoring, and receive real-time updates via secure webhooks. Didit provides clear documentation and examples for integrating webhooks, ensuring you can quickly set up robust, secure communication channels.

We believe in making identity verification accessible and powerful. That's why we offer Free Core KYC, a pay-per-successful check model, and absolutely no setup fees. By leveraging Didit's platform, you gain access to a global, scalable solution that automates trust and orchestrates risk, all while maintaining the highest security standards, including HMAC signature validation for real-time notifications.

Ready to Get Started?

Ready to see Didit in action? Get a free demo today.

Start verifying identities for free with Didit's free tier.

Infrastructure for identity and fraud.

One API for KYC, KYB, Transaction Monitoring, and Wallet Screening. Integrate in 5 minutes.

Ask an AI to summarise this page
Node.js HMAC Validation for Didit Webhooks Security.