Webhook Example

The SDK provides utilities for handling webhooks sent from the Compliance API.

import express from 'express';
import {
  WebhookEvent,
  WebhookEventType,
  WebhookSignatureHeader,
  verifyWebhookSignature
} from '@paytweed/compliance-sdk'; 
import dotenv from 'dotenv';

dotenv.config();

const app = express();

app.use('/webhook', express.raw({ type: 'application/json' }));

const WEBHOOK_SECRET = process.env.COMPLIANCE_SDK_WEBHOOK_SECRET; // Get your secret from env vars

/**
 * Example Curl:
 * curl -X 'POST' 'http://localhost:3000/webhook' \
 *   -H 'host: webhook.site' \
 *   -H 'tracestate: dd=t.tid:680cb60000000000;t.dm:-3;s:2;p:35554b4d8ec726f5' \
 *   -H 'traceparent: 00-680cb600000000003cb0e3a7ccbde59d-35554b4d8ec726f5-01' \
 *   -H 'x-datadog-tags: _dd.p.tid=680cb60000000000,_dd.p.dm=-3' \
 *   -H 'x-datadog-sampling-priority: 2' \
 *   -H 'x-datadog-parent-id: 3843060653510567669' \
 *   -H 'x-datadog-trace-id: 4373245548010792349' \
 *   -H 'accept-encoding: gzip, compress, deflate, br' \
 *   -H 'content-length: 223' \
 *   -H 'user-agent: axios/1.7.7' \
 *   -H 'x-hub-signature-256: sha256=e1cb6899bca87bfde07f4d77cb2b942414a6e4dc0b48acf574bd4983e1472ca4' \
 *   -H 'x-tweed-delivery: 4222e6bf-7e88-4dc2-8f38-dbe861dc2f9c' \
 *   -H 'x-tweed-event: individual_client.updated' \
 *   -H 'x-tweed-hook-id: 6bc02889-1720-4da2-9ebd-b168d57b6b53' \
 *   -H 'content-type: application/json' \
 *   -H 'accept: application/json, text/plain, *\/' \
 *   -d $'{"data":{"id":"ba4a02ff-9eb2-4f4e-8b29-f3880d65ee21","referenceId":"d043bea8-809a-41b3-9edf-b39e0c18a964","updated":{"activityStatus":"NOT_ACTIVE"}},"type":"individual_client.updated","timestamp":"2025-04-26T10:31:28.489Z"}'
 */


app.post('/webhook', (req: any, res: any) => {
  const signature = req.headers[WebhookSignatureHeader] as string;
  const rawBody = req.body; // req.body is a Buffer due to express.raw()

  if (!WEBHOOK_SECRET) {
    console.error('Webhook secret is not configured.');
    return res.status(500).send('Webhook secret not configured.');
  }

  if (!signature) {
    console.warn('Missing signature header');
    return res.status(400).send('Missing signature header.');
  }

  
  try {
    console.log('WEBHOOK_SECRET', WEBHOOK_SECRET);
    console.log('rawBody', rawBody.toString('utf-8'));
    console.log('signature', signature);
    const isValid = verifyWebhookSignature(WEBHOOK_SECRET, rawBody.toString('utf-8'), signature);
    console.log('isValid', isValid);
    if (!isValid) {
      return res.status(403).send('Invalid signature.');
    }
  } catch (error) {
    console.error('Invalid signature:', error);
    return res.status(403).send('Invalid signature.');
  }

  let event: WebhookEvent<any, any>;

  try {
    event = JSON.parse(rawBody.toString('utf-8'));
  } catch (error) {
    console.error('Error parsing webhook JSON:', error);
    return res.status(400).send('Invalid JSON payload.');
  }

  console.log(`Received webhook event: ${event.type}`);
  console.log(`Timestamp: ${event.timestamp}`);
  console.log('Data:', JSON.stringify(event.data, null, 2));

  switch (event.type) {
    case WebhookEventType.INDIVIDUAL_CLIENT_CREATED:
      console.log(`Handling ${event.type}...`);
      // Add specific logic here...
      break;
    case WebhookEventType.INDIVIDUAL_CLIENT_UPDATED:
      // Handle individual client updated event
      console.log(`Handling ${event.type}...`);
      break;
    case WebhookEventType.CORPORATE_CLIENT_CREATED:
      // Handle corporate client created event
      console.log(`Handling ${event.type}...`);
      break;
    case WebhookEventType.CORPORATE_CLIENT_UPDATED:
      // Handle corporate client updated event
      console.log(`Handling ${event.type}...`);
      break;
    case WebhookEventType.ALERT_CREATED:
      // Handle alert created event
      console.log(`Handling ${event.type}...`);
      break;
    case WebhookEventType.ALERT_UPDATED:
      // Handle alert updated event
      console.log(`Handling ${event.type}...`);
      break;
    case WebhookEventType.CASE_CREATED:
      // Handle case created event
      console.log(`Handling ${event.type}...`);
      break;
    case WebhookEventType.CASE_UPDATED:
      // Handle case updated event
      console.log(`Handling ${event.type}...`);
      break;
    default:
      console.warn(`Unhandled event type: ${event.type}`);
  }

  // Acknowledge receipt
  res.status(200).send('Webhook received');
});

const PORT = process.env.PORT || 3000;

app.listen(PORT, () => {
  console.log(`Webhook handler listening on port ${PORT}`);
});

// Export the app for potential testing or use in other modules
export default app;