Node.js Integration
This guide shows how to integrate TxnCheck API into your Node.js application.Installation
Copy
npm install axios
# or
yarn add axios
Copy
npm install --save-dev @types/node
Basic Client Setup
Create a reusable client class:Copy
// 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)
Copy
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
Copy
// 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
Copy
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
Copy
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
Copy
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
Copy
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+)
Copy
// 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
Copy
/**
* 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);
