Workflow guide

Receive payments

The standard receive flow is create customer if needed, create invoice, render the hosted/public invoice payload, then wait for webhook or poll-based settlement.

Happy path sequence

  1. Create a customer if you need a reusable identity or multiple invoices per end user.
  2. Create the invoice with the correct token, chain, network, expiry, and optional tolerance override.
  3. Share the public invoice page or render the public invoice payload in your own UI.
  4. Wait for a signed webhook event first. Poll the invoice only as a fallback.
  5. Persist `invoiceId`, `depositAddress`, and your own order / customer IDs together.

Recurring fintech variant

If your product gives repeat users permanent deposit addresses, PayChainHQ can also auto-detect supported receipts, create auto-generated invoice records, and send the same signed invoice webhooks you already consume.

Endpoints involved

POST/api/v1/businesses/:id/customersx-api-keySandbox + live

Create customer

Create a reusable customer identity with permanent deposit addresses.

When to use it

Use before recurring invoicing or when you want to track invoices by a stable customer record.

Idempotency: recommended

FieldLocationRequiredDescriptionExample / default
idpathYesBusiness ID.-
externalRefbodyYesYour internal customer identifier.-
emailbodyNoOptional customer email.-

Cautions

  • For one-off anonymous payments, create invoices without a customerId instead of creating throwaway customers.
  • Address provisioning depends on the business wallet being ready in the selected environment.

Common errors

  • 401: Missing or invalid API key.
  • 400: Payload shape, query params, or business-state validation failed.

Sample request body

json
{
  "externalRef": "customer_001",
  "email": "customer@example.com"
}

Sample response

json
{
  "id": "cus_123",
  "externalRef": "customer_001",
  "email": "customer@example.com",
  "addresses": [
    { "chainFamily": "eth", "address": "0xE5fa2F71065fD49823D33EdD84ecFD2D6245c916" },
    { "chainFamily": "sol", "address": "8gRLe6GiQx2jo9mQFcbwy34u2W4zD2rN5q4mmD8iP2Mp" }
  ]
}

cURL example

bash
curl -X POST "https://api.paychainhq.io/api/v1/businesses/biz_123/customers" \
  -H "Content-Type: application/json" \
  -H "x-api-key: pk_live_your_business_key" \
  -H "Idempotency-Key: example-request-001" \
  -d '{
  "externalRef": "customer_001",
  "email": "customer@example.com"
}'

Node.js example

ts
const response = await fetch('https://api.paychainhq.io/api/v1/businesses/biz_123/customers', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-api-key': 'pk_live_your_business_key',
    'Idempotency-Key': 'example-request-001',
  },
  body: JSON.stringify({
  "externalRef": "customer_001",
  "email": "customer@example.com"
}),
});
const payload = await response.json();
console.log(payload);
POST/api/v1/businesses/:id/invoicesx-api-keySandbox + live

Create invoice

Create a payable invoice and allocate a deposit address.

When to use it

Use whenever you want a deterministic deposit request with tracked lifecycle state.

Idempotency: recommended

FieldLocationRequiredDescriptionExample / default
idpathYesBusiness ID.-
customerIdbodyNoOptional reusable customer record.-
amountbodyYesInvoice amount in token display units.-
tokenbodyYesSettlement token symbol, for example `USDC`.-
chainbodyYesChain family: `eth`, `btc`, or `sol`.-
networkIdbodyNoSpecific network such as `base-mainnet`.-
expiresInbodyNoInvoice lifetime in seconds.-
underpaymentToleranceBpsbodyNoOptional per-invoice tolerance override.-
underpaymentToleranceMaxUsdbodyNoOptional absolute max shortfall override.-

Flags and defaults

  • underpaymentToleranceBps (default: Business default or 0): Accept small underpayments after confirmations.
  • underpaymentToleranceMaxUsd (default: Business default or 0): Absolute cap that works together with bps tolerance.

Cautions

  • Invoice settlement is asset-matched. Native gas top-ups or unsupported tokens will not settle the invoice.
  • Do not share an invoice until provider registration reports `ready` in the response.

Common errors

  • 401: Missing or invalid API key.
  • 400: Payload shape, query params, or business-state validation failed.

Sample request body

json
{
  "customerId": "cus_123",
  "amount": "150",
  "token": "USDC",
  "chain": "eth",
  "networkId": "base-mainnet",
  "expiresIn": 86400,
  "description": "March onramp settlement",
  "underpaymentToleranceBps": 50,
  "underpaymentToleranceMaxUsd": "0.50"
}

Sample response

json
{
  "id": "inv_123",
  "businessId": "biz_123",
  "customerId": "cus_123",
  "status": "pending",
  "token": "USDC",
  "chain": "eth",
  "networkId": "base-mainnet",
  "amount": {
    "raw": "150000000",
    "decimals": 6,
    "display": "150",
    "symbol": "USDC",
    "chain": "eth",
    "networkId": "base-mainnet"
  },
  "depositAddress": "0xE5fa2F71065fD49823D33EdD84ecFD2D6245c916",
  "expiresAt": "2026-03-15T13:00:00.000Z",
  "underpaymentToleranceBps": 50,
  "underpaymentToleranceMaxUsd": "0.50",
  "providerRegistration": {
    "status": "ready",
    "ready": true,
    "message": "Deposit address is registered and ready to share."
  }
}

cURL example

bash
curl -X POST "https://api.paychainhq.io/api/v1/businesses/biz_123/invoices" \
  -H "Content-Type: application/json" \
  -H "x-api-key: pk_live_your_business_key" \
  -H "Idempotency-Key: example-request-001" \
  -d '{
  "customerId": "cus_123",
  "amount": "150",
  "token": "USDC",
  "chain": "eth",
  "networkId": "base-mainnet",
  "expiresIn": 86400,
  "description": "March onramp settlement",
  "underpaymentToleranceBps": 50,
  "underpaymentToleranceMaxUsd": "0.50"
}'

Node.js example

ts
const response = await fetch('https://api.paychainhq.io/api/v1/businesses/biz_123/invoices', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-api-key': 'pk_live_your_business_key',
    'Idempotency-Key': 'example-request-001',
  },
  body: JSON.stringify({
  "customerId": "cus_123",
  "amount": "150",
  "token": "USDC",
  "chain": "eth",
  "networkId": "base-mainnet",
  "expiresIn": 86400,
  "description": "March onramp settlement",
  "underpaymentToleranceBps": 50,
  "underpaymentToleranceMaxUsd": "0.50"
}),
});
const payload = await response.json();
console.log(payload);
GET/api/v1/invoices/:invoiceIdPublicSandbox + live

Get public invoice payload

Return the payer-facing invoice state, deposit address, and progress details.

When to use it

Use to power a public payment link, hosted payment screen, or payer-side polling loop.

FieldLocationRequiredDescriptionExample / default
invoiceIdpathYesInvoice ID.-

Cautions

  • This payload is public. Do not place internal-only notes or operator metadata into invoice descriptions.
  • Treat it as payer-facing status, not as your sole reconciliation source.

Common errors

  • 404: Requested resource does not exist or is not owned by the business.

Sample response

json
{
  "id": "inv_123",
  "status": "confirming",
  "state": "confirming",
  "amount": {
    "raw": "150000000",
    "decimals": 6,
    "display": "150",
    "symbol": "USDC",
    "chain": "eth",
    "networkId": "base-mainnet"
  },
  "paidAmount": {
    "raw": "149750000",
    "decimals": 6,
    "display": "149.75",
    "symbol": "USDC",
    "chain": "eth",
    "networkId": "base-mainnet"
  },
  "paymentProgress": {
    "percentPaid": "99.83",
    "status": "Paid within tolerance",
    "remainingAmount": {
      "display": "0"
    }
  },
  "depositAddress": "0xE5fa2F71065fD49823D33EdD84ecFD2D6245c916"
}

cURL example

bash
curl -X GET "https://api.paychainhq.io/api/v1/invoices/inv_123" \
  -H "Content-Type: application/json"

Node.js example

ts
const response = await fetch('https://api.paychainhq.io/api/v1/invoices/inv_123', {
  method: 'GET',
  headers: {
    'Content-Type': 'application/json',
  },
});
const payload = await response.json();
console.log(payload);
GET/api/v1/businesses/:id/invoicesx-api-keySandbox + live

List invoices

List invoices for a business with lifecycle and pagination data.

When to use it

Use for admin tables, internal reconciliation, or polling fallback when webhooks are delayed.

FieldLocationRequiredDescriptionExample / default
idpathYesBusiness ID.-
statusqueryNoFilter by invoice status.-
customerIdqueryNoRestrict results to a single customer.-
pagequeryNoPage number.1
limitqueryNoPage size.20

Cautions

  • Expired invoices may remain raw-pending briefly; UI should treat `expiresAt` as authoritative for effective open state.

Common errors

  • 401: Missing or invalid API key.

Sample response

json
{
  "data": [
    {
      "id": "inv_123",
      "status": "pending",
      "token": "USDC",
      "chain": "eth",
      "networkId": "base-mainnet",
      "amount": {
        "raw": "150000000",
        "decimals": 6,
        "display": "150",
        "symbol": "USDC",
        "chain": "eth",
        "networkId": "base-mainnet"
      },
      "expiresAt": "2026-03-15T13:00:00.000Z"
    }
  ],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 1,
    "totalPages": 1,
    "hasMore": false
  }
}

cURL example

bash
curl -X GET "https://api.paychainhq.io/api/v1/businesses/biz_123/invoices?page=1&limit=20" \
  -H "Content-Type: application/json" \
  -H "x-api-key: pk_live_your_business_key"

Node.js example

ts
const response = await fetch('https://api.paychainhq.io/api/v1/businesses/biz_123/invoices?page=1&limit=20', {
  method: 'GET',
  headers: {
    'Content-Type': 'application/json',
    'x-api-key': 'pk_live_your_business_key',
  },
});
const payload = await response.json();
console.log(payload);
GET/api/v1/businesses/invoices/:invoiceIdx-api-keySandbox + live

Get business invoice

Return a business-scoped invoice detail with payment summary and tolerance metadata.

When to use it

Use after webhook receipt or as a fallback read before updating your order state.

FieldLocationRequiredDescriptionExample / default
invoiceIdpathYesInvoice ID.-

Cautions

  • `paidAmount` is always the actual deposited amount. A tolerance-settled invoice does not synthesize the shortfall.

Common errors

  • 401: Missing or invalid API key.
  • 404: Requested resource does not exist or is not owned by the business.

Sample response

json
{
  "id": "inv_123",
  "status": "paid",
  "settledByTolerance": true,
  "underpaymentToleranceBps": 50,
  "underpaymentToleranceMaxUsd": "0.50",
  "toleranceRaw": "500000",
  "toleranceShortfall": {
    "raw": "250000",
    "decimals": 6,
    "display": "0.25",
    "symbol": "USDC",
    "chain": "eth",
    "networkId": "base-mainnet"
  },
  "amount": {
    "raw": "150000000",
    "decimals": 6,
    "display": "150",
    "symbol": "USDC",
    "chain": "eth",
    "networkId": "base-mainnet"
  },
  "paidAmount": {
    "raw": "149750000",
    "decimals": 6,
    "display": "149.75",
    "symbol": "USDC",
    "chain": "eth",
    "networkId": "base-mainnet"
  },
  "depositAddress": "0xE5fa2F71065fD49823D33EdD84ecFD2D6245c916",
  "paymentSummary": {
    "paymentsReceived": 1
  }
}

cURL example

bash
curl -X GET "https://api.paychainhq.io/api/v1/businesses/invoices/inv_123" \
  -H "Content-Type: application/json" \
  -H "x-api-key: pk_live_your_business_key"

Node.js example

ts
const response = await fetch('https://api.paychainhq.io/api/v1/businesses/invoices/inv_123', {
  method: 'GET',
  headers: {
    'Content-Type': 'application/json',
    'x-api-key': 'pk_live_your_business_key',
  },
});
const payload = await response.json();
console.log(payload);
Receive payments | PayChainHQ