Skip to main content
This guide shows you how to set up webhooks to receive real-time notifications about your users’ transactions.

Overview

The Karma Business API acts as a webhook relay for Bridge API events. When Bridge sends webhook events to our central endpoint, we automatically forward them to your configured webhook URL.

Architecture

Bridge API → Karma Business API → Your Webhook URL
             (/v0/webhooks/bridge)

How It Works

  1. Bridge sends webhook event to Karma’s endpoint
  2. Karma receives and validates the event payload
  3. Karma identifies your business based on the external_user_id or customer_id in the event
  4. Karma forwards the complete event to your configured webhook URL
  5. Karma returns 200 OK to Bridge

Step 1: Configure Your Webhook URL

Use the admin API to set a webhook URL for your business:
PUT /v0/customers/:customerId/webhook-url
Authorization: Bearer <ADMIN_SECRET>
Content-Type: application/json

{
  "webhookUrl": "https://yourapp.com/webhooks/karma"
}
Requirements:
  • URL must use HTTPS
  • URL must be publicly accessible
  • Endpoint should return 200 status code quickly

Step 2: Implement Webhook Endpoint

Create an endpoint in your application to receive webhooks:
// Example Express.js endpoint
app.post('/webhooks/karma', express.json(), async (req, res) => {
  const event = req.body;

  // Process the event
  console.log('Received event:', event.event_type, event.event_id);

  // Return 200 quickly to prevent timeouts
  res.status(200).json({ received: true });

  // Process event asynchronously
  processEventAsync(event);
});

Step 3: Handle Different Event Types

Process events based on their type:
function processEventAsync(event) {
  switch (event.event_category) {
    case 'transfer':
      handleTransferEvent(event);
      break;
    case 'virtual_account.activity':
      handleVirtualAccountActivity(event);
      break;
    case 'kyc_link':
      handleKycEvent(event);
      break;
    // ... other event types
  }
}

Step 4: Use Idempotency Keys

Events may be delivered multiple times. Use event_id as an idempotency key:
async function processEventAsync(event) {
  // Check if already processed
  const existing = await db.query(
    'SELECT 1 FROM processed_events WHERE event_id = $1',
    [event.event_id]
  );

  if (existing.rows.length > 0) {
    console.log('Event already processed:', event.event_id);
    return;
  }

  // Process event
  await handleEvent(event);

  // Mark as processed
  await db.query(
    'INSERT INTO processed_events (event_id, processed_at) VALUES ($1, NOW())',
    [event.event_id]
  );
}

Common Event Types

Virtual Account Events

Event TypeDescription
virtual_account.activity.createdFiat deposit received
transfer.createdTransfer initiated
transfer.completedCrypto sent to destination

Liquidation Address Events

Event TypeDescription
liquidation_address.drain.completedCrypto converted and fiat sent
liquidation_address.drain.failedLiquidation failed

KYC Events

Event TypeDescription
kyc_link.approvedKYC verification approved
kyc_link.rejectedKYC verification rejected
customer.createdCustomer account created (after ToS)

Event Structure

Bridge webhook events follow this structure:
{
  api_version: "v0",
  event_id: "wh_123abc456def",              // Unique event ID
  event_developer_id: "dev_111aaa222bbb",   // Developer ID (optional)
  event_category: "transfer",                // Event category
  event_type: "transfer.created",            // Full event type
  event_object_id: "tr_abc123xyz789",       // ID of the object
  event_object_status: "awaiting_funds",     // Status (optional)
  event_object: {                            // Full object data
    id: "tr_abc123xyz789",
    state: "awaiting_funds",
    // ... other fields
  },
  event_object_changes: {                    // Changes from previous event
    state: ["payment_submitted", "funds_received"]
  },
  event_created_at: "2025-07-22T11:26:00.000Z"
}

Headers Sent to Your Endpoint

When forwarding events, Karma includes these headers:
Content-Type: application/json
X-Webhook-Event-Id: wh_123abc456def
X-Webhook-Event-Type: transfer.created
X-Webhook-Event-Category: transfer

Best Practices

Respond with 200 OK within 5 seconds. Process events asynchronously if needed to avoid timeouts.
Webhook events may be delivered more than once. Use the event_id to ensure idempotent processing.
Your webhook URL must use HTTPS in production to encrypt event data.
If your processing fails, log the error but still return 200 to prevent retries. Store the event for manual review.

Testing Webhooks Locally

For testing webhooks locally, use tools like ngrok to expose your local server to the internet:
ngrok http 3000
# Use the provided HTTPS URL as your webhook URL

Next Steps