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

Building a Didit Webhook Listener with Fastify & PostgreSQL

Learn how to build a robust and secure webhook listener for Didit's real-time identity verification notifications using Node.js, Fastify, and PostgreSQL.

By DiditUpdated
building-a-didit-webhook-listener-with-fastify-postgresql.png

Secure Webhook IntegrationImplementing HMAC-SHA256 signature verification is crucial for ensuring the authenticity and integrity of Didit webhook payloads, protecting your application from forged requests and maintaining data security.

Real-time Data ProcessingUtilize webhooks to receive instant notifications on identity verification session status changes, enabling your application to react immediately and automate user onboarding or risk management workflows.

Scalable Data StorageLeverage PostgreSQL to efficiently store and manage webhook data, allowing for robust querying, auditing, and integration with your existing user management systems, ensuring data persistence and reliability.

Didit's Modular AdvantageDidit’s modular identity platform and comprehensive webhook system simplify integration, providing developers with clean APIs and real-time data flows to build flexible, AI-native KYC solutions with Free Core KYC and no setup fees.

Understanding Didit Webhooks and Their Importance

In the world of identity verification, real-time feedback is paramount. Whether you're onboarding new users, preventing fraud, or ensuring compliance, knowing the status of a verification session as it happens is critical. This is where webhooks come in. Didit's webhooks provide an automated way to receive notifications about events occurring within your identity verification workflows, such as a session completing, failing, or requiring further action.

Instead of constantly polling the Didit API for updates, which can be inefficient and lead to delays, webhooks push data directly to your configured endpoint. This event-driven architecture ensures your application always has the most up-to-date information, allowing for immediate processing and a smoother user experience. For example, once a user successfully completes Didit's ID Verification and Passive & Active Liveness checks, a webhook can trigger the next step in your onboarding flow instantly.

Didit offers configurable webhook versions, with v3 being the recommended standard for its comprehensive payload structure. You can set your webhook URL, version, and even define data retention policies directly through the Didit Business Console or via the API, giving you full control over your data flow and compliance obligations.

Setting Up Your Fastify Webhook Listener

Fastify is a fast and low-overhead web framework for Node.js, making it an excellent choice for building performant webhook listeners. Here's how to get started:

1. Project Setup and Dependencies

First, initialize your Node.js project and install the necessary packages:

mkdir didit-webhook-listener
cd didit-webhook-listener
npm init -y
npm install fastify dotenv pg crypto

Create an .env file to store your Didit webhook secret key and PostgreSQL connection details:

DIDIT_WEBHOOK_SECRET=your_didit_webhook_secret_key
DATABASE_URL=postgresql://user:password@host:port/database

You can retrieve your DIDIT_WEBHOOK_SECRET from the Didit Business Console under App Settings or via the Didit API's GET /v3/webhook/ endpoint.

2. Fastify Server Configuration

Create an app.js file. Fastify needs the raw body for signature verification, so we'll configure it to not parse JSON automatically for our webhook route.

require('dotenv').config();
const fastify = require('fastify')({ logger: true });
const crypto = require('crypto');
const { Client } = require('pg');

const DIDIT_WEBHOOK_SECRET = process.env.DIDIT_WEBHOOK_SECRET;
const DATABASE_URL = process.env.DATABASE_URL;

const pgClient = new Client({ connectionString: DATABASE_URL });

// Register a content parser for 'application/json' that leaves the body raw for specific routes
fastify.addContentTypeParser('application/json', { parseAs: 'buffer' }, function (req, body, done) {
  if (req.raw.url === '/webhooks/didit') {
    done(null, body); // Leave raw for webhook endpoint
  } else {
    try {
      const json = JSON.parse(body.toString());
      done(null, json);
    } catch (error) {
      error.statusCode = 400;
      done(error, undefined);
    }
  }
});

fastify.post('/webhooks/didit', async (request, reply) => {
  const signature = request.headers['x-signature'];
  const timestamp = request.headers['x-timestamp'];
  const rawBody = request.body;

  if (!signature || !timestamp || !rawBody) {
    reply.code(400).send({ message: 'Missing signature, timestamp, or body' });
    return;
  }

  // 1. Verify HMAC-SHA256 signature
  const expectedSignature = crypto
    .createHmac('sha256', DIDIT_WEBHOOK_SECRET)
    .update(`${timestamp}.${rawBody.toString()}`)
    .digest('hex');

  if (expectedSignature !== signature) {
    reply.code(403).send({ message: 'Invalid signature' });
    return;
  }

  // 2. Validate timestamp (e.g., within 5 minutes to prevent replay attacks)
  const fiveMinutesAgo = Date.now() - (5 * 60 * 1000);
  if (parseInt(timestamp) < fiveMinutesAgo) {
    reply.code(403).send({ message: 'Timestamp too old' });
    return;
  }

  // 3. Parse JSON body after verification
  let payload;
  try {
    payload = JSON.parse(rawBody.toString());
  } catch (error) {
    reply.code(400).send({ message: 'Invalid JSON payload' });
    return;
  }

  fastify.log.info('Received Didit webhook:', payload);

  // 4. Process the webhook payload (e.g., store in DB, update user status)
  try {
    await pgClient.query(
      'INSERT INTO webhooks (event_id, event_type, payload, received_at) VALUES ($1, $2, $3, NOW())',
      [payload.id, payload.event, JSON.stringify(payload)]
    );
    // Example: Update user status based on payload.status
    // await pgClient.query(
    //   'UPDATE users SET verification_status = $1 WHERE didit_session_id = $2',
    //   [payload.status, payload.session_id]
    // );
    reply.code(200).send({ message: 'Webhook received and processed' });
  } catch (dbError) {
    fastify.log.error('Database error:', dbError);
    reply.code(500).send({ message: 'Internal server error' });
  }
});

const start = async () => {
  try {
    await pgClient.connect();
    fastify.log.info('Connected to PostgreSQL');
    await fastify.listen({ port: 3000, host: '0.0.0.0' });
  } catch (err) {
    fastify.log.error(err);
    process.exit(1);
  }
};

start();

Storing Webhook Data in PostgreSQL

Persisting webhook data is crucial for auditing, debugging, and ensuring data integrity. PostgreSQL is a robust relational database ideal for this task.

1. Database Schema

First, create a table to store your webhook events. This schema provides a good starting point:

CREATE TABLE webhooks (
    id SERIAL PRIMARY KEY,
    event_id VARCHAR(255) UNIQUE NOT NULL, -- Didit's unique event ID
    event_type VARCHAR(255) NOT NULL,
    payload JSONB NOT NULL, -- Store the full JSON payload
    received_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

2. Integrating with Fastify

As shown in the app.js example, we integrate the pg client to connect to your PostgreSQL database and insert the incoming webhook payload. The payload JSONB NOT NULL column is perfect for storing the entire JSON object, allowing for flexible querying later without predefined schema changes for every new field Didit might introduce.

Remember to handle potential database errors gracefully. The example above includes a basic try-catch block for database operations.

Securing Your Webhook Endpoint

Security is paramount when dealing with sensitive identity verification data. Didit employs HMAC-SHA256 signatures to ensure that incoming webhooks are legitimate and untampered.

1. HMAC Signature Verification

Every Didit webhook request includes two crucial headers: X-Signature and X-Timestamp. The X-Signature header contains an HMAC-SHA256 signature generated using your unique webhook secret and the raw request body concatenated with the timestamp. Your listener must:

  1. Retrieve your DIDIT_WEBHOOK_SECRET.
  2. Extract the X-Signature and X-Timestamp headers.
  3. Reconstruct the signed payload: `${timestamp}.${rawBody}`.
  4. Generate your own HMAC-SHA256 signature using your secret and the reconstructed payload.
  5. Compare your generated signature with the X-Signature from the request. If they don't match, reject the request.

The provided Fastify example demonstrates this exact process, ensuring that only authentic Didit webhooks are processed.

2. Timestamp Validation

The X-Timestamp header is vital for preventing replay attacks. A replay attack occurs when an attacker intercepts a legitimate webhook and resends it later. By checking that the timestamp is recent (e.g., within 5 minutes of the current time), you can mitigate this risk. The Fastify code snippet includes a check to ensure the timestamp is not too old.

How Didit Helps

Didit's platform is designed to make integrating identity verification seamless and secure. Our modular architecture allows you to plug-and-play various identity checks, from ID Verification for robust document analysis to NFC Verification for high-security ePassport/eID checks, and AML Screening & Monitoring for compliance. The webhook system is a core component of this modularity, providing real-time updates that enable you to orchestrate complex KYC workflows without constant polling.

Didit's AI-native approach means our systems are constantly learning and adapting, providing superior accuracy and fraud detection. The clear API documentation and developer-first mentality, combined with features like Free Core KYC and no setup fees, make it easy to get started. Our webhooks provide structured identity data, allowing your application to react intelligently to verification outcomes and automate trust at scale, globally. Whether you need to verify age using Didit's Age Estimation or ensure a user is a live person with Passive & Active Liveness, Didit provides the tools and the real-time feedback necessary for a robust identity solution.

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
Build a Didit Webhook Listener with Fastify & PostgreSQL.