SDK

The ICPay SDK provides a typed API for payments on the Internet Computer. It supports public client-side operations with a publishable key and private server-side operations with a secret key.

Intro

  • Public operations: fetch account info, verified ledgers, pricing, start payments from a connected wallet.
  • Private operations: account details, payment history, transactions. Use only on servers with your secret key.

Get your keys

Create an account in the icpay.org to obtain your Publishable Key (client) and Secret Key (server). Keep your Secret Key private.

Quickstart

Initialize the SDK with your publishable key on the client. Only when using createPayment or createPaymentUsd, you must provide an actorProvider and a connected wallet.

Client setup (TypeScript)

import { Icpay, IcpayError } from '@ic-pay/icpay-sdk'

const icpay = new Icpay({
  publishableKey: process.env.NEXT_PUBLIC_ICPAY_PK!,

  // actorProvider + connected wallet are required only when calling
  // createPayment or createPaymentUsd (e.g. Plug, Internet Identity, Oisy)
})

try {
  // Add your code here
} catch (e) {
  if (e instanceof IcpayError) {
    // Handle gracefully
  }
}

Create a payment

There are two common ways to pay: fixed token amount or USD amount automatically converted to tokens.

Primary input going forward is the token shortcode. Each verified token ledger exposes a unique, human‑readable shortcode (e.g. ic_icp, base_usdcoin). Prefer sending tokenShortcode instead of symbol/ledgerCanisterId/chainId. Legacy fields still work for backward compatibility but are deprecated.

Note on chains:

  • chainId (optional, UUID): Target chain for the payment intent. If omitted, the intent defaults to the Internet Computer chain. Use this when directing payments to another supported chain.

Send fixed token amount (preferred: tokenShortcode)

import { Icpay } from '@ic-pay/icpay-sdk'

// If you want a drop-in wallet selector, use the widget's helper:
// import { createWalletSelect } from '@ic-pay/icpay-widget'
// const walletSelect = createWalletSelect()

const icpay = new Icpay({
  publishableKey: process.env.NEXT_PUBLIC_ICPAY_PK!,
  actorProvider: (canisterId, idl) => /* return agent-js actor from your wallet */
    walletSelect.getActor({ canisterId, idl, requiresSigning: true, anon: false }),
  connectedWallet: { owner: '<principal-id>' },
})

const tx = await icpay.createPayment({
  tokenShortcode: 'ic_icp', // primary way (server resolves token ledger/chain from shortcode)
  amount: '150000000', // smallest unit (e.g. 1.5 ICP with 8 decimals)
  metadata: { myProductId: '511234', myOrderId: '511234', anyInternalField: 'allowed' },
})

Send by USD amount (auto conversion, preferred: tokenShortcode)

const res = await icpay.createPaymentUsd({
  usdAmount: 5,
  tokenShortcode: 'ic_icp',
  metadata: { context: 'tip-jar', myProductId: '511234', myOrderId: '511234', anyInternalField: 'allowed' },
})

X402 payments (cards-enabled flow with fallback)

// Attempts X402 flow first; falls back to regular USD flow if not available
const out = await icpay.createPaymentX402Usd({
  usdAmount: 10,
  tokenShortcode: 'ic_icp',
  metadata: { context: 'subscription' },
})

// If X402 responds with Payment Required (402), the SDK will handle header signing
// and settlement, then wait for terminal status, emitting events along the way.

Events are emitted throughout the flow when enableEvents is true. See Events section below.

Configuration

IcpayConfig options:

  • publishableKey (string): Public key for client operations.
  • secretKey (string): Server-only key for private endpoints.
  • environment ('development' | 'production'): Default 'production'.
  • apiUrl (string): Default https://api.icpay.org.
  • connectedWallet (object): Connected wallet/principal for signing.
  • icHost (string): IC network host for agent-js. Default https://icp-api.io.
  • actorProvider (fn): (canisterId, idl) => Actor used to sign ICRC transfers.
  • debug (boolean): Verbose logging in SDK internals.
  • enableEvents (boolean): Emit SDK lifecycle events.

Price helpers and ledger info:

// Get list of Verified Ledgers
const ledgers = await icpay.getVerifiedLedgers()
// Get Ledger Canister Id by Symbol string. eg. ckUSDC or ICP
const bySymbol = await icpay.getLedgerCanisterIdBySymbol('ICP')
// Get all information (eg. decimals, current price, ...) about a ledger by passing a Ledger Canister Id (eg. ICP ledger id is 'ryjl3-tyaaa-aaaaa-aaaba-cai')
const info = await icpay.getLedgerInfo('ryjl3-tyaaa-aaaaa-aaaba-cai')
// Fetch all ledgers with current price for 1 token unit
const priced = await icpay.getAllLedgersWithPrices()
// Get number of token that is needed for a fixed price in USD
const calc = await icpay.calculateTokenAmountFromUSD({ usdAmount: 10, ledgerCanisterId: info.canisterId })

Types reference (balances)

type LedgerBalance = {
  ledgerId: string
  ledgerName: string
  ledgerSymbol: string
  canisterId: string
  eip3009Version?: string | null
  x402Accepts?: boolean
  balance: string                 // smallest unit
  formattedBalance: string        // human-readable
  decimals: number
  currentPrice?: number
  lastPriceUpdate?: Date
  lastUpdated: Date
  // Chain metadata (when available)
  chainId?: string
  chainName?: string | null
  rpcUrlPublic?: string | null
  chainUuid?: string | null
  // Required amount helpers (when amount/amountUsd passed)
  requiredAmount?: string
  requiredAmountFormatted?: string
  hasSufficientBalance?: boolean
  logoUrl?: string | null
}

Wallet helpers

Helpers to connect and manage wallets when initiating payments:

  • showWalletModal() → Prompts user to connect with the first available provider.
  • connectWallet(providerId) → Connect a specific provider: 'internet-identity' | 'oisy' | 'plug'.
  • getWalletProviders() → Returns supported providers.
  • isWalletProviderAvailable(providerId) → Check availability in current environment.
  • getAccountAddress() → Principal/address of connected wallet.
  • disconnectWallet() / isWalletConnected() / getConnectedWalletProvider()
const providers = icpay.getWalletProviders()
if (icpay.isWalletProviderAvailable('plug')) {
  await icpay.connectWallet('plug')
}
const principal = icpay.getAccountAddress()

Public API reference

  • Account and chains:
    • getAccountInfo() → Public account info: id/live/canister/branding.
    • getVerifiedLedgers() → Verified ledgers with price metadata.
    • getChains() → Enabled chains (IC/EVM) with RPC/explorer data.
    • getLedgerCanisterIdBySymbol(symbol) → Resolve ICPay-verified symbol to canister id.
  • Ledger info and prices:
    • getLedgerInfo(ledgerCanisterId) → Detailed ledger metadata, price, decimals.
    • getAllLedgersWithPrices() → All ledgers including current price.
    • calculateTokenAmountFromUSD({ usdAmount, ledgerCanisterId | ledgerSymbol }) → Price quote to smallest unit.
  • Balances:
    • getSingleLedgerBalance(ledgerCanisterId) → Connected wallet balance + price context for one ledger.
    • getExternalWalletBalances({ network, address|principal, ... }) → Aggregate balances for an external EVM/IC wallet. Useful for showing “Pay with …” choices and sufficiency checks.
  • Payments:
    • createPayment(request) → Token-denominated payment.
    • createPaymentUsd(request) → USD-denominated payment (auto conversion).
    • createPaymentX402Usd(request) → X402 flow with automatic fallback to regular path.
    • notifyPaymentIntentOnRamp({ paymentIntentId, ... }) → Poll an onramp intent until terminal status.
    • triggerTransactionSync(canisterTransactionId) → Ask ICPay to sync a canister tx to API DB now.

External wallet balances (public)

const all = await icpay.getExternalWalletBalances({
  network: 'evm',               // 'evm' | 'ic'
  address: '0xabc...',          // or principal: 'aaaa-bbbb...'
  amountUsd: 10,                // optional: compute requiredAmount per ledger
  chainShortcodes: ['base-sep'],// optional filter
  tokenShortcodes: ['base_usdcoin'], // optional filter by token shortcode
})
for (const b of all.balances) {
  console.log(b.ledgerSymbol, b.formattedBalance, b.hasSufficientBalance)
}

Deprecated inputs (still supported)

  • symbol, ledgerCanisterId, and chainId continue to work, but new integrations should use tokenShortcode. The server derives the ledger and chain from the shortcode automatically.

Single-ledger balance (connected wallet)

const b = await icpay.getSingleLedgerBalance('ryjl3-tyaaa-aaaaa-aaaba-cai')
console.log(b.ledgerSymbol, b.formattedBalance, b.currentPrice)

Low-level utilities

Advanced helpers used internally by flows; you can use them for custom UX:

  • pollTransactionStatus(canisterId, txId, accountCanisterId, indexReceived, intervalMs?, maxAttempts?) → Anonymous polling for status.
  • notifyLedgerTransaction(icpayCanisterId, ledgerCanisterId, blockIndex) → Notify ICPay canister about a ledger tx.
  • getTransactionStatusPublic(icpayCanisterId, canisterTxId, indexReceived, accountCanisterId) → Public status endpoint via actor.
  • getTransactionByFilter(canisterTxId) → Fallback lookup by scanning transactions.
  • sendFundsToLedger(ledgerCanisterId, toPrincipal, amount, memo?, host?) → ICRC-1 transfer (requires actorProvider).
  • packEvmId(accountCanisterId, intentCode) → Build bytes32 id used by EVM PaymentProcessor.
// Example: pack EVM id (bytes32) for custom contract calls
const idHex = icpay.packEvmId('42', 1234)

Events

Enable with enableEvents: true. Subscribe via on or standard addEventListener.

Event names:

  • icpay-sdk-error
  • icpay-sdk-transaction-created
  • icpay-sdk-transaction-updated
  • icpay-sdk-transaction-completed
  • icpay-sdk-transaction-failed
  • icpay-sdk-transaction-mismatched
  • icpay-sdk-connect-wallet
  • icpay-sdk-method-start
  • icpay-sdk-method-success
  • icpay-sdk-method-error
const unsubscribe = icpay.on('icpay-sdk-transaction-completed', (detail) => {
  // e.g., show success toast, update UI
})

icpay.on('icpay-sdk-transaction-mismatched', (detail) => {
  // detail.requestedAmount, detail.paidAmount
})

// Later
unsubscribe()

Types reference (payments)

Payment objects now include split-aware fields when applicable:

type SdkPayment = {
  id: string
  accountId: string
  paymentIntentId: string
  transactionId: string | null
  transactionSplitId?: string | null
  canisterTxId: number | null
  amount: string
  ledgerCanisterId: string
  ledgerTxId?: string | null
  accountCanisterId?: number | null
  basePaymentAccountId?: string | null
  status: 'pending' | 'completed' | 'failed' | 'canceled' | 'refunded' | 'mismatched'
  // Optional clarity fields on webhook payloads and some responses
  requestedAmount?: string | null // from payment_intent.amount
  paidAmount?: string | null // from transaction.amount
  invoiceId: string | null
  metadata: Record<string, unknown>
  createdAt: string
  updatedAt: string
}

Public responses (publishable key) expose the same fields via PaymentPublic where returned.

Alternatively, DOM-style listeners:

const onCompleted = (evt: any) => {
  const detail = evt && typeof evt === 'object' ? (evt as any).detail ?? evt : evt
  // e.g., show success toast, update UI
}

icpay.addEventListener('icpay-sdk-transaction-completed', onCompleted)

// Later
icpay.removeEventListener('icpay-sdk-transaction-completed', onCompleted)

Event reference

EventWhen it firesPayload (shape)
icpay-sdk-errorAny SDK error is emitted (if enableEvents is true)IcpayError instance (code, message, details?, retryable?, userAction?)
icpay-sdk-transaction-createdAfter creating a payment intent before transfer{ paymentIntentId, amount, ledgerCanisterId, expectedSenderPrincipal }
icpay-sdk-transaction-updatedWhile polling/awaiting status; intermediate updatesTransactionResponse-like: { transactionId, status, amount, recipientCanister, timestamp, description?, metadata?, payment? }
icpay-sdk-transaction-completed

Was this page helpful?