Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.lux-core.io/llms.txt

Use this file to discover all available pages before exploring further.

Node.js Examples

Complete Node.js examples for integrating with the LuxCore API using axios.

Installation

npm install axios

LuxCore Client Class

const axios = require('axios');
const crypto = require('crypto');

class LuxCoreClient {
  static BASE_URL = 'https://api.lux-core.io/api/v1';

  constructor(apiKey = process.env.LUXCORE_API_KEY) {
    if (!apiKey) {
      throw new Error('API key is required');
    }

    this.client = axios.create({
      baseURL: LuxCoreClient.BASE_URL,
      headers: {
        'X-API-Key': apiKey,
        'Content-Type': 'application/json'
      }
    });

    // Add response interceptor for error handling
    this.client.interceptors.response.use(
      (response) => response.data,
      (error) => {
        const err = new LuxCoreError(
          error.response?.data?.message || error.message,
          error.response?.status,
          error.response?.data?.error
        );
        throw err;
      }
    );
  }

  // Payments
  async createPayment(data) {
    return this.client.post('/payments', data);
  }

  async getPayment(paymentId) {
    return this.client.get(`/payments/${paymentId}`);
  }

  async listPayments(filters = {}) {
    return this.client.get('/payments', { params: filters });
  }

  async cancelPayment(paymentId) {
    return this.client.post(`/payments/${paymentId}/cancel`);
  }

  // Balance
  async getBalance(currency) {
    const params = currency ? { currency } : {};
    return this.client.get('/balance', { params });
  }

  async getAllBalances() {
    return this.client.get('/balance/all');
  }

  // Webhooks
  async createWebhook(data) {
    return this.client.post('/webhooks', data);
  }

  async listWebhooks() {
    return this.client.get('/webhooks');
  }

  async deleteWebhook(webhookId) {
    return this.client.delete(`/webhooks/${webhookId}`);
  }

  // Static method for webhook verification
  // IMPORTANT: Pass the raw request body string, not a parsed/re-serialized object
  static verifyWebhookSignature(rawBody, signature, timestamp, secret) {
    // Check timestamp (reject if older than 5 minutes)
    const currentTime = Math.floor(Date.now() / 1000);
    if (Math.abs(currentTime - timestamp) > 300) {
      throw new Error('Webhook timestamp too old');
    }

    // Calculate expected signature using raw body
    const payloadToSign = `${timestamp}.${rawBody}`;
    const expectedSignature = 'hmac_sha256=' + crypto
      .createHmac('sha256', secret)
      .update(payloadToSign)
      .digest('hex');

    // Compare signatures (timing-safe)
    if (!crypto.timingSafeEqual(
      Buffer.from(signature),
      Buffer.from(expectedSignature)
    )) {
      throw new Error('Invalid webhook signature');
    }

    return true;
  }
}

class LuxCoreError extends Error {
  constructor(message, statusCode, errorCode) {
    super(message);
    this.name = 'LuxCoreError';
    this.statusCode = statusCode;
    this.errorCode = errorCode;
  }
}

module.exports = { LuxCoreClient, LuxCoreError };

Usage Examples

Create a Deposit Payment

const { LuxCoreClient, LuxCoreError } = require('./luxcore');

const client = new LuxCoreClient();

async function createDeposit() {
  try {
    const payment = await client.createPayment({
      amount: 100000, // $1,000.00 MXN (in centavos)
      currency: 'MXN',
      method: 'spei',
      type: 'deposit',
      merchant_reference: `order_${Date.now()}`,
      customer: {
        name: 'Juan Perez',
        email: 'juan@example.com',
        phone: '+525551234567'
      },
      metadata: {
        order_id: '12345',
        source: 'web'
      }
    });

    console.log('Payment created:', payment.transaction_id);
    console.log('Status:', payment.status);
    
    if (payment.bank_details) {
      console.log('Bank:', payment.bank_details.bank_name);
      console.log('Account:', payment.bank_details.account_number);
      console.log('Holder:', payment.bank_details.account_holder);
    }

    return payment;
  } catch (error) {
    if (error instanceof LuxCoreError) {
      console.error(`API Error (${error.statusCode}): ${error.message}`);
    } else {
      console.error('Unexpected error:', error);
    }
  }
}

createDeposit();

Create a Withdrawal (Payout)

async function createPayout() {
  const payout = await client.createPayment({
    amount: 50000,
    currency: 'MXN',
    method: 'spei',
    type: 'withdrawal',
    merchant_reference: `payout_${Date.now()}`,
    customer: {
      name: 'Finance Team',
      email: 'finance@company.com'
    },
    payout: {
      recipient_name: 'Maria Garcia',
      bank_account: '012345678901234567',
      bank_code: '002',
      reference: 'PAYOUT-001'
    }
  });

  console.log('Payout created:', payout.transaction_id);
  return payout;
}

List Payments

async function listRecentPayments() {
  const result = await client.listPayments({
    status: 'completed',
    limit: 20,
    created_at_from: '2025-01-01T00:00:00Z'
  });

  console.log(`Found ${result.total} payments`);
  console.log(`Pagination: page ${result.pagination?.offset || 0}, total pages: ${result.pagination?.total_pages}`);

  for (const payment of result.data) {
    console.log(`${payment.transaction_id}: ${payment.amount} ${payment.currency}`);
  }
}

Webhook Handler (Express)

const express = require('express');
const { LuxCoreClient } = require('./luxcore');

const app = express();

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

const WEBHOOK_SECRET = process.env.LUXCORE_WEBHOOK_SECRET;

app.post('/webhooks/luxcore', (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  const timestamp = parseInt(req.headers['x-webhook-timestamp']);

  // Verify using raw body (not re-serialized JSON)
  try {
    LuxCoreClient.verifyWebhookSignature(
      req.rawBody,
      signature,
      timestamp,
      WEBHOOK_SECRET
    );
  } catch (error) {
    console.error('Webhook verification failed:', error.message);
    return res.status(400).json({ error: error.message });
  }

  // Process event — use envelope structure
  const event = req.headers['x-webhook-event'];
  const webhookId = req.headers['x-webhook-id'];
  const payment = payload.data.payment;
  console.log(`Received ${event} for payment ${payment.id} (webhook: ${webhookId})`);

  switch (event) {
    case 'payment.completed':
      // Handle completed payment
      console.log(`Payment ${payment.id} completed!`);
      // Update database, send confirmation email, etc.
      break;

    case 'payment.failed':
      // Handle failed payment
      console.log(`Payment ${payment.id} failed: ${payment.failure_reason}`);
      break;

    case 'payment.cancelled':
      // Handle cancelled payment
      console.log(`Payment ${payment.id} cancelled`);
      break;

    default:
      console.log(`Unhandled event: ${event}`);
  }

  res.status(200).json({ received: true });
});

app.listen(3000, () => {
  console.log('Webhook server listening on port 3000');
});

TypeScript Version

import axios, { AxiosInstance } from 'axios';
import crypto from 'crypto';

interface PaymentRequest {
  amount: number;
  currency: string;
  method: string;
  type: 'deposit' | 'withdrawal' | 'deposit_pp' | 'withdrawal_pp';
  merchant_reference: string;
  customer: {
    name: string;
    email: string;
    phone?: string;
    external_id?: string;
  };
  payout?: {
    recipient_name: string;
    bank_account: string;
    bank_code: string;
    reference?: string;
    account_type?: string;
    beneficiary_tax_id?: string;
    description?: string;
    payid?: string;
    payid_type?: 'email' | 'phone' | 'abn';
  };
  webhook_url?: string;
  webhook_events?: string[];
  customer_id?: number;
  description?: string;
  traffic_type?: 'primary' | 'secondary' | 'vip';
  metadata?: Record<string, unknown>;
}

interface Payment {
  transaction_id: string;
  status: string;
  amount: number;
  currency: string;
  method: string | null;
  type: string;
  fee_amount?: number;
  net_amount?: number;
  error_code?: string;
  error_message?: string;
  created_at: string;
  bank_details?: {
    amount: string;
    purpose: string;
    currency: string;
    bank_name: string;
    swift_code: string | null;
    account_holder: string;
    account_number: string;
  };
}

class LuxCoreClient {
  private client: AxiosInstance;
  private static BASE_URL = 'https://api.lux-core.io/api/v1';

  constructor(apiKey: string = process.env.LUXCORE_API_KEY!) {
    this.client = axios.create({
      baseURL: LuxCoreClient.BASE_URL,
      headers: {
        'X-API-Key': apiKey,
        'Content-Type': 'application/json'
      }
    });
  }

  async createPayment(data: PaymentRequest): Promise<Payment> {
    const response = await this.client.post<Payment>('/payments', data);
    return response.data;
  }

  async getPayment(paymentId: string): Promise<Payment> {
    const response = await this.client.get<Payment>(`/payments/${paymentId}`);
    return response.data;
  }

  // IMPORTANT: Pass the raw request body string, not a parsed/re-serialized object
  static verifyWebhookSignature(
    rawBody: string,
    signature: string,
    timestamp: number,
    secret: string
  ): boolean {
    const currentTime = Math.floor(Date.now() / 1000);
    if (Math.abs(currentTime - timestamp) > 300) {
      throw new Error('Webhook timestamp too old');
    }

    const payloadToSign = `${timestamp}.${rawBody}`;
    const expectedSignature = 'hmac_sha256=' + crypto
      .createHmac('sha256', secret)
      .update(payloadToSign)
      .digest('hex');

    return crypto.timingSafeEqual(
      Buffer.from(signature),
      Buffer.from(expectedSignature)
    );
  }
}

export { LuxCoreClient, PaymentRequest, Payment };

Error Handling

const { LuxCoreClient, LuxCoreError } = require('./luxcore');

async function safeCreatePayment(data, attempt = 0, maxRetries = 3) {
  const client = new LuxCoreClient();

  try {
    return await client.createPayment(data);
  } catch (error) {
    if (error instanceof LuxCoreError) {
      switch (error.statusCode) {
        case 401:
          console.error('Invalid API key');
          break;
        case 400:
          console.error('Validation error:', error.message);
          break;
        case 429:
          if (attempt >= maxRetries) throw error;
          console.error('Rate limited - implementing backoff');
          await sleep(60000 * Math.pow(2, attempt));
          return safeCreatePayment(data, attempt + 1, maxRetries);
        case 500:
        case 503:
          if (attempt >= maxRetries) throw error;
          console.error('Server error - retrying');
          await sleep(5000 * Math.pow(2, attempt));
          return safeCreatePayment(data, attempt + 1, maxRetries);
        default:
          console.error(`API error (${error.statusCode}): ${error.message}`);
      }
    }
    throw error;
  }
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}