Skip to main content

Node.js Integration

This guide shows how to integrate TxnCheck API into your Node.js application.

Installation

npm install axios
# or
yarn add axios
For TypeScript support:
npm install --save-dev @types/node

Basic Client Setup

Create a reusable client class:
// txncheck.ts
import axios, { AxiosInstance, AxiosError } from 'axios';
import crypto from 'crypto';

export interface TxnCheckConfig {
  apiKey: string;
  baseUrl?: string;
  secretKey?: string;
  timeout?: number;
}

export interface VerificationResult {
  statusCode: number;
  requestId: string;
  method?: string;
  status: string;
  result?: Record<string, any>;
  error?: Record<string, any>;
  stepStatuses?: Record<string, string>;
  createdAt?: string;
  completedAt?: string;
}

export interface AcceptedResponse {
  statusCode: number;
  message: string;
  requestId: string;
  status: string;
}

export type RequestStatus = 'QUEUED' | 'PROCESSING' | 'COMPLETED' | 'FAILED' | 'PARTIAL';

export class TxnCheckClient {
  private client: AxiosInstance;
  private secretKey?: string;

  constructor(config: TxnCheckConfig) {
    this.secretKey = config.secretKey;
    
    this.client = axios.create({
      baseURL: config.baseUrl || 'https://api.txncheck.in/api/v1',
      timeout: config.timeout || 30000,
      headers: {
        'X-API-Key': config.apiKey,
        'Content-Type': 'application/json',
      },
    });

    // Add request signing interceptor
    if (this.secretKey) {
      this.client.interceptors.request.use((config) => {
        const timestamp = Date.now().toString();
        const body = config.data ? JSON.stringify(config.data) : '';
        const path = config.url || '';
        const method = (config.method || 'GET').toUpperCase();
        
        const message = `${timestamp}.${method}.${path}.${body}`;
        const signature = crypto
          .createHmac('sha256', this.secretKey!)
          .update(message)
          .digest('hex');

        config.headers['X-Timestamp'] = timestamp;
        config.headers['X-Signature'] = signature;
        return config;
      });
    }
  }

  /**
   * Get UPI VPAs linked to a mobile number
   */
  async upiByMobile(
    mobile: string, 
    options: { sync?: boolean } = {}
  ): Promise<VerificationResult | AcceptedResponse> {
    const { data } = await this.client.post('/upi-by-mobile', {
      mobile,
      async: options.sync === true ? false : true,
    });
    return data;
  }

  /**
   * Get KYC data by mobile number
   */
  async kycByMobile(
    mobile: string, 
    options: { sync?: boolean } = {}
  ): Promise<VerificationResult | AcceptedResponse> {
    const { data } = await this.client.post('/kyc-by-mobile', {
      mobile,
      async: options.sync === true ? false : true,
    });
    return data;
  }

  /**
   * Check VPAs against blocklist
   */
  async vpaChargebackCheck(
    vpas: string[], 
    options: { sync?: boolean } = {}
  ): Promise<VerificationResult | AcceptedResponse> {
    const { data } = await this.client.post('/vpa-chargeback-check', {
      vpas,
      async: options.sync === true ? false : true,
    });
    return data;
  }

  /**
   * Full verification: UPI + KYC + Chargeback check
   */
  async fullCheck(
    mobile: string, 
    options: { sync?: boolean } = {}
  ): Promise<VerificationResult | AcceptedResponse> {
    const { data } = await this.client.post('/full-check', {
      mobile,
      async: options.sync === true ? false : true,
    });
    return data;
  }

  /**
   * Bulk check VPAs against blocklist
   */
  async bulkVpaCheck(
    vpas: string[], 
    options: { sync?: boolean } = {}
  ): Promise<VerificationResult | AcceptedResponse> {
    const { data } = await this.client.post('/bulk/vpa-chargeback-check', {
      vpas,
      async: options.sync === true ? false : true,
    });
    return data;
  }

  /**
   * Get request status and results
   */
  async getRequestStatus(requestId: string): Promise<VerificationResult> {
    const { data } = await this.client.get(`/requests/${requestId}`);
    return data;
  }

  /**
   * Poll for request completion
   */
  async waitForResult(
    requestId: string,
    options: { pollInterval?: number; maxWait?: number } = {}
  ): Promise<VerificationResult> {
    const { pollInterval = 2000, maxWait = 60000 } = options;
    const terminalStatuses: RequestStatus[] = ['COMPLETED', 'FAILED', 'PARTIAL'];
    const startTime = Date.now();

    while (Date.now() - startTime < maxWait) {
      const result = await this.getRequestStatus(requestId);
      
      if (terminalStatuses.includes(result.status as RequestStatus)) {
        return result;
      }

      await new Promise((resolve) => setTimeout(resolve, pollInterval));
    }

    throw new Error(`Request ${requestId} did not complete within ${maxWait}ms`);
  }
}

Usage Examples

Async Mode (Default)

import { TxnCheckClient } from './txncheck';

const client = new TxnCheckClient({
  apiKey: 'fb_your_api_key_here',
});

// Submit request
const response = await client.upiByMobile('+919876543210');
console.log(`Request ID: ${response.requestId}`);

// Poll for result
const result = await client.waitForResult(response.requestId);
console.log(`Status: ${result.status}`);
console.log(`UPI addresses:`, result.result?.upi);

Sync Mode

// Get result immediately (waits up to 30 seconds)
const result = await client.upiByMobile('+919876543210', { sync: true });

if (result.status === 'COMPLETED') {
  console.log(`Name: ${result.result?.name}`);
  console.log(`UPI addresses:`, result.result?.upi);
}

Full Check

const result = await client.fullCheck('+919876543210', { sync: true });

if (result.status === 'COMPLETED') {
  const { upiByMobile, kycByMobile, vpaChargebackCheck } = result.result!;
  
  // UPI data
  console.log(`Name: ${upiByMobile.name}`);
  console.log(`VPAs: ${upiByMobile.upi.join(', ')}`);
  
  // KYC data
  console.log(`PAN: ${kycByMobile.pan}`);
  console.log(`DOB: ${kycByMobile.dob}`);
  
  // Blocklist
  console.log(`Blocklisted: ${vpaChargebackCheck.summary.blocklisted}`);
}

VPA Blocklist Check

const vpas = ['user1@upi', 'user2@paytm', 'suspicious@ybl'];
const result = await client.vpaChargebackCheck(vpas, { sync: true });

if (result.status === 'COMPLETED') {
  // Blocklisted VPAs
  for (const vpa of result.result!.blocklisted) {
    console.log(`⚠️ BLOCKED: ${vpa.vpa}`);
  }
  
  // Clean VPAs
  for (const vpa of result.result!.clean) {
    console.log(`✓ Clean: ${vpa.vpa}`);
  }
}

Error Handling

import { AxiosError } from 'axios';

export class TxnCheckError extends Error {
  statusCode: number;
  errorType: string;

  constructor(statusCode: number, message: string, errorType: string) {
    super(message);
    this.name = 'TxnCheckError';
    this.statusCode = statusCode;
    this.errorType = errorType;
  }
}

function handleError(error: AxiosError): never {
  if (error.response) {
    const data = error.response.data as Record<string, any>;
    throw new TxnCheckError(
      error.response.status,
      data.message || 'Unknown error',
      data.error || 'Error'
    );
  }
  throw error;
}

// Usage with retry logic
async function verifyWithRetry(
  client: TxnCheckClient,
  mobile: string,
  maxRetries = 3
): Promise<VerificationResult> {
  let lastError: Error | null = null;

  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await client.upiByMobile(mobile, { sync: true }) as VerificationResult;
    } catch (error) {
      lastError = error as Error;
      
      if (error instanceof AxiosError) {
        const status = error.response?.status;
        
        // Don't retry client errors (except rate limit)
        if (status && status >= 400 && status < 500 && status !== 429) {
          handleError(error);
        }
        
        // Exponential backoff
        const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000);
        console.log(`Retry ${attempt}/${maxRetries} in ${delay}ms...`);
        await new Promise((resolve) => setTimeout(resolve, delay));
      } else {
        throw error;
      }
    }
  }

  throw lastError;
}

Webhook Signature Verification

import crypto from 'crypto';
import { Request, Response, NextFunction } from 'express';

export function verifyWebhookSignature(
  payload: string | Buffer,
  signature: string,
  timestamp: string,
  secretKey: string,
  maxAgeSeconds = 300
): boolean {
  // Check timestamp freshness
  const webhookTime = parseInt(timestamp, 10);
  const currentTime = Date.now();
  const ageMs = currentTime - webhookTime;

  if (ageMs > maxAgeSeconds * 1000) {
    return false; // Webhook too old
  }

  // Compute expected signature
  const payloadString = typeof payload === 'string' ? payload : payload.toString('utf8');
  const message = `${timestamp}.${payloadString}`;
  const expected = crypto
    .createHmac('sha256', secretKey)
    .update(message)
    .digest('hex');

  // Constant-time comparison
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// Express middleware
export function webhookMiddleware(secretKey: string) {
  return (req: Request, res: Response, next: NextFunction) => {
    const signature = req.headers['x-webhook-signature'] as string;
    const timestamp = req.headers['x-webhook-timestamp'] as string;

    if (!signature || !timestamp) {
      return res.status(401).json({ error: 'Missing signature headers' });
    }

    // Need raw body for signature verification
    const rawBody = (req as any).rawBody || JSON.stringify(req.body);

    if (!verifyWebhookSignature(rawBody, signature, timestamp, secretKey)) {
      return res.status(401).json({ error: 'Invalid signature' });
    }

    next();
  };
}

// Express setup
import express from 'express';

const app = express();
const WEBHOOK_SECRET = 'your_webhook_secret';

// Capture raw body for signature verification
app.use(express.json({
  verify: (req: any, res, buf) => {
    req.rawBody = buf;
  }
}));

app.post('/webhook/txncheck', webhookMiddleware(WEBHOOK_SECRET), (req, res) => {
  const { event, data } = req.body;

  switch (event) {
    case 'request.completed':
      console.log('Request completed:', data.requestId);
      // Process result...
      break;
    case 'request.failed':
      console.log('Request failed:', data.requestId);
      // Handle failure...
      break;
  }

  res.json({ status: 'ok' });
});

Native Fetch Client (Node.js 18+)

// txncheck-fetch.ts
import crypto from 'crypto';

export class TxnCheckFetchClient {
  private baseUrl: string;
  private apiKey: string;
  private secretKey?: string;

  constructor(config: TxnCheckConfig) {
    this.baseUrl = config.baseUrl || 'https://api.txncheck.in/api/v1';
    this.apiKey = config.apiKey;
    this.secretKey = config.secretKey;
  }

  private async request<T>(
    method: string,
    path: string,
    body?: Record<string, any>
  ): Promise<T> {
    const url = `${this.baseUrl}${path}`;
    const headers: Record<string, string> = {
      'X-API-Key': this.apiKey,
      'Content-Type': 'application/json',
    };

    // Sign request if secret key is configured
    if (this.secretKey) {
      const timestamp = Date.now().toString();
      const bodyStr = body ? JSON.stringify(body) : '';
      const message = `${timestamp}.${method}.${path}.${bodyStr}`;
      const signature = crypto
        .createHmac('sha256', this.secretKey)
        .update(message)
        .digest('hex');

      headers['X-Timestamp'] = timestamp;
      headers['X-Signature'] = signature;
    }

    const response = await fetch(url, {
      method,
      headers,
      body: body ? JSON.stringify(body) : undefined,
    });

    if (!response.ok) {
      const error = await response.json().catch(() => ({}));
      throw new TxnCheckError(
        response.status,
        error.message || response.statusText,
        error.error || 'Error'
      );
    }

    return response.json();
  }

  async upiByMobile(mobile: string, options: { sync?: boolean } = {}) {
    return this.request<VerificationResult>('POST', '/upi-by-mobile', {
      mobile,
      async: !options.sync,
    });
  }

  // ... other methods similar to axios client
}

Complete Integration Example

/**
 * Complete example: E-commerce checkout verification
 */
import { TxnCheckClient, VerificationResult } from './txncheck';

interface VerificationOutcome {
  verified: boolean;
  riskScore: number;
  details: {
    name?: string;
    panVerified: boolean;
    vpaLinked: boolean;
    vpaCount: number;
    blocklisted: boolean;
  };
  error?: string;
}

async function verifyCustomerForCheckout(
  client: TxnCheckClient,
  mobile: string,
  paymentVpa: string
): Promise<VerificationOutcome> {
  const outcome: VerificationOutcome = {
    verified: false,
    riskScore: 0,
    details: {
      panVerified: false,
      vpaLinked: false,
      vpaCount: 0,
      blocklisted: false,
    },
  };

  try {
    // Step 1: Full verification
    console.log(`Starting verification for ${mobile}`);
    const verification = await client.fullCheck(mobile, { sync: true }) as VerificationResult;

    if (verification.status !== 'COMPLETED') {
      console.warn(`Verification incomplete: ${verification.status}`);
      outcome.riskScore = 100;
      return outcome;
    }

    // Step 2: Extract data
    const upiData = verification.result?.upiByMobile || {};
    const kycData = verification.result?.kycByMobile || {};
    const blocklist = verification.result?.vpaChargebackCheck || {};

    // Step 3: Check if payment VPA belongs to user
    const userVpas: string[] = upiData.upi || [];
    const vpaLinked = userVpas
      .map((v: string) => v.toLowerCase())
      .includes(paymentVpa.toLowerCase());

    if (!vpaLinked) {
      console.warn(`Payment VPA ${paymentVpa} not linked to ${mobile}`);
      outcome.riskScore += 50;
    }

    // Step 4: Check blocklist
    const blocklistedVpas = (blocklist.blocklisted || [])
      .map((v: any) => v.vpa.toLowerCase());
    
    if (blocklistedVpas.includes(paymentVpa.toLowerCase())) {
      console.error(`Payment VPA ${paymentVpa} is BLOCKLISTED!`);
      outcome.riskScore = 100;
      outcome.details.blocklisted = true;
      return outcome;
    }

    // Step 5: Build result
    outcome.verified = outcome.riskScore < 50;
    outcome.details = {
      name: kycData.fullName || upiData.name,
      panVerified: Boolean(kycData.pan),
      vpaLinked,
      vpaCount: userVpas.length,
      blocklisted: false,
    };

    console.log(`Verification complete. Risk score: ${outcome.riskScore}`);
    return outcome;

  } catch (error) {
    console.error('Verification failed:', error);
    outcome.riskScore = 100;
    outcome.error = error instanceof Error ? error.message : 'Unknown error';
    return outcome;
  }
}

// Usage
async function main() {
  const client = new TxnCheckClient({
    apiKey: 'fb_your_api_key_here',
  });

  const result = await verifyCustomerForCheckout(
    client,
    '+919876543210',
    'user@upi'
  );

  if (result.verified) {
    console.log('✓ Customer verified, proceed with payment');
    console.log(`  Name: ${result.details.name}`);
  } else {
    console.log(`⚠️ Verification failed. Risk score: ${result.riskScore}`);
    if (result.details.blocklisted) {
      console.log('  Reason: VPA is blocklisted');
    }
  }
}

main().catch(console.error);

Next Steps