Skip to main content

SDK Guide

The PayChain SDK is the recommended integration path for Node.js and TypeScript backends. It wraps the same public REST API with typed helpers, safer payout split handling, idempotency support, error mapping, and webhook verification utilities.
Positioning note: Use the SDK when your backend runs Node.js or TypeScript. Use the REST API directly when you need another language, full HTTP control, or your own internal SDK.

Install

npm install @paychainhq/sdk

Create a client

import { PayChain } from '@paychainhq/sdk';

const paychain = new PayChain({
  apiKey: process.env.PAYCHAIN_API_KEY!,
  businessId: process.env.PAYCHAIN_BUSINESS_ID!,
  environment: 'live'
});

What the SDK covers

  • Customers.
  • Invoices.
  • Payout route attachment.
  • Dynamic payout recipients.
  • Withdrawals.
  • Webhooks.
  • Balances.
  • Transactions.
  • Networks.
  • Tokens.

What the SDK does not cover

  • Admin APIs.
  • Dashboard-session flows.
  • Route-template creation, archive, or delete.
  • API key creation, rotation, or revocation.
  • Billing mutation flows.
  • Internal gas sponsorship controls.
  • Wallet provider, network provider, signer, sponsored-gas, or treasury-private implementation details.
Security note: The SDK is server-side only. Do not bundle it into browser or mobile apps with API keys.

Idempotency

Every mutating operation should include an idempotency key.
await paychain.invoices.create(
  {
    amount: '100.00',
    token: 'USDC',
    chain: 'evm',
    networkId: 'base-mainnet'
  },
  { idempotencyKey: 'order_123_invoice' }
);

Customer deposit addresses

For reusable customer deposit-address flows, ensure the customer has an address for the rail before showing payment instructions.
const customerAddress = await paychain.customers.ensureAddress(
  'customer_123',
  {
    chain: 'sol',
    networkId: 'sol-mainnet'
  },
  { idempotencyKey: 'customer_123_sol_address' }
);

if (
  customerAddress.address.chainFamily !== 'sol' ||
  customerAddress.address.networkId !== 'sol-mainnet'
) {
  throw new Error('Solana deposit address is not available for this customer');
}

console.log(customerAddress.address.address);
Address selection note: Solana flows must use a Solana base58 address returned for chainFamily: "sol" and networkId: "sol-mainnet". Never fall back to ethAddress, the first customer address, or any 0x... address.

Invoice confirmation progress

Use invoices.get to poll canonical invoice state while a payment is waiting for required network confirmations.
const invoice = await paychain.invoices.get('invoice_123');

if (invoice.confirmationProgress?.status === 'confirming') {
  console.log({
    current: invoice.confirmationProgress.current,
    required: invoice.confirmationProgress.required,
    remaining: invoice.confirmationProgress.remaining,
    percent: invoice.confirmationProgress.percent,
    txHash: invoice.confirmationProgress.txHash
  });
}
Fulfillment note: Wait for paid or overpaid invoice status before fulfilling. Confirmation progress is for customer-facing visibility while settlement finality is still pending.

Webhook verification

Webhook verification must use the raw request body.
import { verifyWebhookSignature } from '@paychainhq/sdk';

const verified = verifyWebhookSignature({
  rawBody,
  signature: request.headers['x-webhook-signature'],
  secret: process.env.PAYCHAIN_WEBHOOK_SECRET!
});

if (!verified) {
  throw new Error('Invalid PayChain webhook signature');
}
Raw body note: Do not parse and re-stringify JSON before verifying. Use the exact bytes PayChain sent.

When to use REST instead

Use REST directly when:
  • Your backend is not Node.js or TypeScript.
  • You need language-specific HTTP instrumentation.
  • You want to build your own wrapper.
  • You need to inspect exact request and response payloads.
  • You are debugging endpoint-level behavior.