Transaction Approval Webhook

BitPowr Transaction Approval Webhook Guide

Overview

The BitPowr Transaction Approval Webhook allows you to programmatically approve or reject transactions before they are processed. This powerful feature enables custom approval logic, compliance checks, fraud detection, and transaction validation based on your business requirements.

How to Setup

  1. Navigate to the account Policy Page
  2. Find Transaction Approval Webhook URL section
  3. Enter your webhook URL (e.g., https://yourdomain.com/webhook/transaction-approval)
  4. Check Enable checkbox
  5. Click Save changes

How Transaction Approval Works

Operation Flows

When a new outgoing Transaction is created
    ↓
BitPowr sends webhook payload to your URL
    ↓
Your system processes the payload
    ↓
Return HTTP 200 (Approve) or 400+ (Reject)
    ↓
Transaction moves to APPROVED or REJECTED status

Response Handling

  • HTTP 200: Transaction approved, processing continues
  • HTTP 400+: Transaction rejected, processing stops
  • Any Error: Automatic rejection (timeout, network error, etc.)

Webhook Payload Structure

When a transaction requires approval, BitPowr sends a POST request to your webhook URL with the following payload:

{
  "transactionId": "6680c116-c803-4e46-aa99-2f261cd216ac",
  "walletId": "e3455-15b5-46ad-84de-34555",
  "assetType": "USDC_ETH",
  "amount": "1000.00",
	"to": [
    {address: "0x742d35Cc6644C0532925a3b8C17DAb6F2c8f1234", value: 1000}
  ],
  "from": [],
	"fromUtxo": [],
  "description": "API-VALIDATED: Monthly vendor payment #12345",
  "category": "PAYMENT",
  "transactionType": "TRANSFER",
  "source": "API",
  "ipAddress": "192.168.1.100",
  "userAgent": "MyApp/1.0 (https://myapp.com)",
  "timestamp": "2025-01-15T10:30:00Z",
  "metadata": {
    "customerId": "customer-premium-789",
    "subAccountId": "vendor-payments",
    "memo": "Invoice #INV-2025-001",
    "idempotencyKey": "vendor-payment-12345-2025-01"
  }
}

Payload Field Descriptions

FieldTypeDescription
transactionIdstringUnique BitPowr transaction identifier. In this case the UID
walletIdstringWallet ID initiating the transaction
assetTypestringType of asset being transferred
toarrayDestination tos. Array of (address, value)
fromarrayList of addresses to spend from
fromUtxoarrayList of UTXO to spend from
descriptionstringTransaction description
categorystringTransaction category (withdrawal, gas_station, etc.)
transactionTypestringType of transaction (TRANSFER, CONTRACT_INTERACTION, etc.)
sourcestringSource of transaction (API, Dashboard, System)
ipAddressstringIP address of the request origin
userAgentstringUser agent string from the request
timestampstringISO timestamp of transaction creation
metadataobjectAdditional transaction metadata

Approval Response Formats

Approve Transaction

HTTP/1.1 200 OK
Content-Type: application/json

{
  "approved": true,
  "message": "Transaction approved - matches database record",
  "approvalCode": "AUTO-APPROVED-001",
  "approvedBy": "system-validator",
  "approvedAt": "2025-01-15T10:30:05Z"
}

Reject Transaction

HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "approved": false,
  "message": "Transaction rejected - not found in database",
  "rejectionCode": "DB-VALIDATION-FAILED",
  "rejectedBy": "system-validator",
  "rejectedAt": "2025-01-15T10:30:05Z"
}

Implementation Examples

1. Database Validation Webhook

This example checks if API transactions exist in your database before approval.

const express = require('express');
const app = express();

app.use(express.json());

app.post('/webhook/transaction-approval', async (req, res) => {
  try {
    const {
      transactionId,
      source,
      description,
      amount,
      address,
      assetType,
      metadata
    } = req.body;

    // Only validate API transactions
    if (source !== 'API') {
      return res.status(200).json({
        approved: true,
        message: 'Non-API transaction auto-approved',
        approvalCode: 'AUTO-APPROVED-NON-API'
      });
    }

    // Check if transaction exists in database
    const dbTransaction = await findTransactionInDatabase({
      idempotencyKey: metadata.idempotencyKey,
      amount: amount,
      address: address,
      assetType: assetType
    });

    if (dbTransaction) {
      // Transaction found - approve
      await updateTransactionStatus(dbTransaction.id, 'APPROVED');
      
      return res.status(200).json({
        approved: true,
        message: 'Transaction approved - found in database',
        approvalCode: 'DB-VALIDATION-PASSED',
        approvedBy: 'automated-system',
        dbTransactionId: dbTransaction.id
      });
    } else {
      // Transaction not found - reject
      await logRejection(transactionId, 'DB-VALIDATION-FAILED');
      
      return res.status(400).json({
        approved: false,
        message: 'Transaction rejected - not found in database',
        rejectionCode: 'DB-VALIDATION-FAILED',
        rejectedBy: 'automated-system'
      });
    }
  } catch (error) {
    console.error('Webhook error:', error);
    
    // Return rejection on any error
    return res.status(500).json({
      approved: false,
      message: 'Transaction rejected - system error',
      rejectionCode: 'SYSTEM-ERROR',
      error: error.message
    });
  }
});

const findTransactionInDatabase = async (criteria) => {
  // Your database query logic here
  return await db.transactions.findOne({
    where: {
      idempotencyKey: criteria.idempotencyKey,
      amount: criteria.amount,
      status: 'PENDING_APPROVAL'
    }
  });
};

2. Description-Based Validation

This example uses specific text in the description to determine which transactions to validate.

app.post('/webhook/transaction-approval', async (req, res) => {
  try {
    const {
      transactionId,
      source,
      description,
      amount,
      address,
      assetType
    } = req.body;

    // Check if description contains validation marker
    const requiresValidation = description.includes('API-VALIDATED:');
    
    if (source === 'API' && requiresValidation) {
      // Extract order ID from description
      const orderIdMatch = description.match(/API-VALIDATED:.*#(\w+)/);
      const orderId = orderIdMatch ? orderIdMatch[1] : null;
      
      if (!orderId) {
        return res.status(400).json({
          approved: false,
          message: 'Invalid description format - missing order ID',
          rejectionCode: 'INVALID-FORMAT'
        });
      }
      
      // Validate order exists
      const order = await findOrderById(orderId);
      
      if (!order) {
        return res.status(400).json({
          approved: false,
          message: `Order ${orderId} not found`,
          rejectionCode: 'ORDER-NOT-FOUND'
        });
      }
      
      // Validate order details match transaction
      if (order.amount !== parseFloat(amount) || order.address !== address) {
        return res.status(400).json({
          approved: false,
          message: 'Order details do not match transaction',
          rejectionCode: 'DETAILS-MISMATCH'
        });
      }
      
      // All validations passed
      return res.status(200).json({
        approved: true,
        message: `Transaction approved for order ${orderId}`,
        approvalCode: 'ORDER-VALIDATED',
        orderId: orderId
      });
    }
    
    // Non-validated transactions auto-approve
    return res.status(200).json({
      approved: true,
      message: 'Transaction auto-approved - no validation required',
      approvalCode: 'AUTO-APPROVED'
    });
    
  } catch (error) {
    return res.status(500).json({
      approved: false,
      message: 'System error during validation',
      rejectionCode: 'SYSTEM-ERROR'
    });
  }
});

3. Multi-Factor Approval System

This example implements different approval rules based on amount and source.

app.post('/webhook/transaction-approval', async (req, res) => {
  try {
    const {
      transactionId,
      source,
      amount,
      assetType,
      ipAddress,
      userAgent,
      metadata
    } = req.body;

    const transactionAmount = parseFloat(amount);
    
    // High-value transaction checks
    if (transactionAmount > 10000) {
      const manualApproval = await checkManualApproval(transactionId);
      if (!manualApproval) {
        return res.status(400).json({
          approved: false,
          message: 'High-value transaction requires manual approval',
          rejectionCode: 'MANUAL-APPROVAL-REQUIRED'
        });
      }
    }
    
    // IP whitelist check for API transactions
    if (source === 'API') {
      const isWhitelisted = await checkIPWhitelist(ipAddress);
      if (!isWhitelisted) {
        return res.status(400).json({
          approved: false,
          message: 'IP address not whitelisted for API transactions',
          rejectionCode: 'IP-NOT-WHITELISTED'
        });
      }
    }
    
    // Rate limiting check
    const recentTransactions = await getRecentTransactions(
      metadata.customerId, 
      '1hour'
    );
    
    if (recentTransactions.length > 10) {
      return res.status(400).json({
        approved: false,
        message: 'Rate limit exceeded - too many transactions',
        rejectionCode: 'RATE-LIMIT-EXCEEDED'
      });
    }
    
    // All checks passed
    return res.status(200).json({
      approved: true,
      message: 'Transaction approved - all checks passed',
      approvalCode: 'MULTI-FACTOR-APPROVED',
      checks: {
        amount: transactionAmount <= 10000 ? 'auto' : 'manual',
        ipWhitelist: 'passed',
        rateLimit: 'passed'
      }
    });
    
  } catch (error) {
    return res.status(500).json({
      approved: false,
      message: 'Error during approval process',
      rejectionCode: 'SYSTEM-ERROR'
    });
  }
});

Use Cases

1. Transaction Database Validation

Scenario: Ensure all API transactions exist in your system before processing.

Implementation:

  • Check source === 'API'
  • Lookup transaction by idempotencyKey or custom identifier
  • Approve if found, reject if not found

2. Order Validation

Scenario: Validate transactions against order records.

Implementation:

  • Extract order ID from description
  • Validate order exists and matches transaction details
  • Check order status is ready for payment

3. Fraud Detection

Scenario: Detect suspicious transactions based on patterns.

Implementation:

  • Check IP address against whitelist/blacklist
  • Analyze transaction frequency and amounts
  • Validate user agent patterns

4. Compliance Checking

Scenario: Ensure transactions meet regulatory requirements.

Implementation:

  • Check transaction amounts against limits
  • Validate recipient addresses against sanctions lists
  • Verify customer KYC status

5. Budget Control

Scenario: Enforce spending limits by customer or category.

Implementation:

  • Track spending by customer ID
  • Check against budget limits
  • Reject transactions exceeding limits

6. Time-Based Restrictions

Scenario: Only allow transactions during business hours.

Implementation:

  • Check transaction timestamp
  • Validate against business hours
  • Reject transactions outside allowed times

Webhook Requirements

  • HTTPS Required: Webhook URL must use HTTPS in production
  • Response Time: Must respond within 30 seconds
  • Status Codes: 200 for approval, 400+ for rejection
  • Content-Type: application/json recommended

This comprehensive webhook system gives you complete control over transaction approval while maintaining security and reliability.