Skip to main content

Pre-Requisites

  • You have an active Conduit account with API access
  • You have API credentials (API Key and Secret)
  • You have understood our Webhooks Developer Section
In this guide, we will learn how to create your first webhook. A webhook is a URL that Conduit will call when certain events occur. For example, when a transaction is created, Conduit will call the webhook URL with the transaction details.

Registering Webhook Endpoint

To start receiving real-time updates, you must register your webhook endpoint with Conduit. You can do this by sending a POST request to the /webhooks API endpoint with your webhook details.

Request Example

curl -X POST https://api.conduit.financial/webhooks \
-H "X-API-Key: your_api_key" \
-H "X-API-Secret: your_api_secret" \
-H "Content-Type: application/json" \
-d '{
"url": "your_webhook_url",
"status": "enabled",
"events": ["your_subscribed_event"],
"organizationId": "your_client_id"
}'
Request Parameters:
ParameterTypeDescription
urlstringYour webhook endpoint URL to receive events
statusstringStatus of the webhook, typically “enabled” when creating
eventsArrayArray of event types you want to subscribe to (refer to events ) section on the supported events)
organizationIdstringYour client or organization identifier
Example Response
{
  "id": "whk_31BbTJ0ORWosbmona9WP0ThJYT5",
  "url": "https://www.example.com/webhooks",
  "status": "enabled",
  "organizationId": "client_2zsj24vsMLU9emYzu2rZXlJhQs",
  "events": [
    "counterparty.active"
  ]
}
** Response Field description**
FieldDescription
idThe unique identifier of the webhook entry
urlYour webhook endpoint URL to receive events
eventsArray of event types you subscribed to ( refer to events
organizationIdYour client or organization identifier
statusStatus of the webhook, “enabled”

Retrieve Your Webhook Secret

Before you can verify incoming webhook requests, you need the secret associated with your webhook. This secret is used to validate the authenticity of each request from Conduit, protecting your system from unauthorized or forged events. To find your webhook secret:
  1. Log into your Conduit Dashboard
  2. Navigate to the Webhooks tab.
  3. Locate the webhook you created and click on it
  4. Click on it to view the Key Secret field this what you use in your signature verification process.

Verifying Webhook Requests

For security,** every webhook request from Conduit must be verified before processing**. Use the secret you retrieved above to validate the HMAC SHA-256 signature included in each request header. The following NodeJS handler middleware demonstrates how to implement a secure webhook receiver that:
  • Extracts the signature headers from the incoming request.
  • Recreates the HMAC signature using your webhook secret for verification.
const express = require('express');
const crypto = require('crypto');
const app = express();

// Middleware to parse JSON bodies
app.use(express.json());

// Middleware to validate incoming webhook requests
const validateWebhook = (req, res, next) => {
  // Extract signature and timestamp headers from the request
  const signature = req.headers['conduit-signature'];
  const timestamp = req.headers['conduit-signature-timestamp'];

  // Reject request if headers are missing
  if (!signature || !timestamp) {
    console.error('Missing signature headers');
    return res.status(401).send({ error: 'Missing signature headers' });
  }

  // Get your webhook secret from environment variables
  const secret = process.env.WEBHOOK_SECRET;
  if (!secret) {
    console.error('Webhook secret is not configured');
    return res.status(500).send({ error: 'Server configuration error' });
  }

  // Convert request body to string for signing
  const payload = JSON.stringify(req.body);
  // Create the string to sign: "<timestamp>.<payload>"
  const stringToSign = `${timestamp}.${payload}`;

  try {
    // Compute HMAC SHA256 hash using your secret
    const expectedSignature = crypto
      .createHmac('sha256', secret)
      .update(stringToSign)
      .digest('hex');

    // Compare expected signature with the one sent by Conduit
 
    if (expectedSignature !== signature) {
      console.error('Invalid signature');
      return res.status(401).send({ error: 'Invalid signature' });
    }

    // Signature verified — proceed to next middleware or route handler
    next();
  } catch (error) {
    console.error('Webhook validation error:', error);
    return res.status(401).send({ error: 'Webhook validation failed' });
  }
};

// Webhook endpoint with validation middleware applied
app.post('/', validateWebhook, (req, res) => {
  res.status(200).send({ message: 'Webhook received' });
});

// Start server
const PORT = process.env.PORT || 9876;
app.listen(PORT, () => {
  console.log(`Webhook client listening on port ${PORT}`);
});

Structure of Webhook Payload

Each webhook you receive from Conduit follows a consistent structure.
{
  "event": "WebhookEventType", 
  "version": "1.0",
  "data": {
      // object with the data 
    }
  }
}
Field descriptions:
FieldTypeDescription
eventstringThe type of event that occurred, e.g., counterparty.active, customer.created.
versionstringThe webhook payload version. Use this to ensure compatibility with future updates.
dataobjectContains the main event data, this can counterparty, transaction or customer.
The follow are the examples of the payloads that you will receive based for each resource type we currently support.

Counterparty Event

This is response you will get when counterparty is compliance review
{
  "event": "counterparty.in_compliance_review",
  "version": "1.0",
  "data": {
    "counterparty": {
      "id": "cp_31C7TAhHBpmrTYNXnzn6lrzlzqY",
      "type": "business",
      "businessName": "Test",
      "website": "https://www.counterparty.com",
      "address": {
        "streetLine1": "Flat 1 , 4th Street, Ibex Hill ",
        "streetLine2": "Lusaka",
        "city": "Kitwe",
        "state": "Lusaka",
        "postalCode": "10101",
        "country": "ALB"
      },
      "paymentMethods": [
        {
          "id": "bank_31C7TCE61yBrxPFELkdrwcD56hX",
          "type": "bank",
          "rail": [
            "swift"
          ],
          "bankName": "FNB",
          "accountType": "checking",
          "accountOwnerName": "Test",
          "accountNumber": "4658579489498",
          "routingNumber": "",
          "swiftCode": "EMPOALTR",
          "currency": "USD",
          "address": {
            "streetLine1": "Albanian Alps",
            "city": "Tirana",
            "state": "Shkodër County",
            "postalCode": "101010",
            "country": "ALB"
          }
        }
      ],
      "documents": [
        {
          "documentId": "doc_31C7T3kpDDL0B3xXB8H2WZwq9ZH",
          "documentPurpose": "transaction_justification",
          "documentType": "contract",
          "documentName": "Customer KYB.pdf",
          "uploadedAt": "2025-08-12T16:30:05.044Z"
        }
      ],
      "status": "in_compliance_review",
      "createdAt": "2025-08-12T16:30:05.725Z",
      "updatedAt": "2025-08-12T16:30:05.725Z",
      "clientId": "client_31C4ON5QZPrSnpGcUTDzQAPVXGS"
    }
  }
}

Customer Event

This an example event when a customer is created
{
  "event": "customer.created",
  "version": "1.0",
  "data": {
    "customer": {
      "id": "cus_31C4ghJdDTNf3OdeMmb98q4868JU4",
      "type": "business",
      "businessLegalName": "Testing",
      "businessTradeName": null,
      "industry": null,
      "email": null,
      "phone": null,
      "website": null,
      "registeredDate": null,
      "registeredAddress": null,
      "operatingAddress": null,
      "kybLink": "https://verify-sandbox.aiprise.com/?business_onboarding_session_id=dc004638-4258-4676-8b28-c6901896aafeca3",
      "kybLinkExpiration": "2025-11-10T16:07:12.841Z",
      "kybLinkGeneratedAt": "2025-08-12T16:07:12.841Z",
      "kybCompletedAt": null,
      "status": "created",
      "organizationId": "31C4ON5QZPrSn89pGcUTDzQAPVXGS",
      "tags": null,
      "kybData": null,
      "industryData": null,
      "taxIdentificationNumber": null,
      "businessEntityId": null,
      "isSubsidiary": false,
      "kybSetupBy": "client",
      "createdAt": "2025-08-12T16:07:12.370Z",
      "updatedAt": "2025-08-12T16:07:12.370Z",
      "deletedAt": null,
      "isFinancialInstitution": null,
      "identifications": [],
      "documents": []
    }
  }
}

Transaction Event

This an example of a transaction event for a successful deposit
{
  "event": "transaction.completed",
  "version": "1.0",
  "data": {
    "transaction": {
      "type": "deposit",
      "id": "trxn_31C6aorcaHHEl1jLbSzXRTbj7eC",
      "status": "COMPLETED",
      "source": {
        "address": "0x0000000000000000000000000000000000000000",
        "amount": {
          "assetType": "USDC",
          "decimals": 6,
          "standardDecimals": 6,
          "amount": "10000000000",
          "assetTypeNetwork": {
            "assetType": "USDC",
            "networkId": "ethereum:goerli"
          }
        }
      },
      "destination": {
        "id": "acct_31C4OLZH4qdXXXizursw2FsDcmb",
        "amount": {
          "assetType": "USDC",
          "decimals": 6,
          "standardDecimals": 6,
          "amount": "10000000000",
          "assetTypeNetwork": {
            "assetType": "USDC",
            "networkId": "ethereum:goerli"
          }
        }
      },
      "purpose": null,
      "reference": null,
      "createdAt": "2025-08-12T16:22:53.782Z",
      "completedAt": "2025-08-12T16:23:03.007Z",
      "clientId": "client_31C4ON5QZPrSnpGcUTDzQAPVXGS"
    }
  }
}
For list of all the events that you can subscribe to, please check our Webhooks Supported Events Developer Section.
I