Introduction

The Accept Crypto HERE API lets you accept crypto, track payments, and manage payouts. Base URL:

https://your-host/v1      # self-hosted; locally: http://localhost:3000/v1

All requests and responses are JSON. Amounts are decimal strings (e.g. "49.99"). The only Accept Crypto HERE fee is a flat 0.25% (0.5% when a payment is converted to another coin); blockchain network fees on payouts are paid by you.

Authentication

Every /v1 request is authenticated with a key as a Bearer token (or the x-api-key header). There are two key types:

KeyPrefixUse
Secret keypic_live_Server-side only. Full access — payments, balances, payouts, withdrawals. Stored as a hash, shown once.
Publishable keypic_pub_Client-safe. Can only create payments, read a payment, and quote. Anything sensitive returns 403.
curl https://your-host/v1/balance?asset=BTC \
  -H "Authorization: Bearer pic_live_your_secret_key"

You also get a webhook secret (whsec_…) for verifying IPN callbacks. Find all three in the dashboard under Settings → API keys, or get them programmatically (below). Rotating immediately invalidates the previous keys:

POST /v1/account/apikey
# -> { "secret_key": "pic_live_…", "publishable_key": "pic_pub_…" }   (secret shown once)

Quick start

Create a merchant account + key in one call (no auth required), then start creating payments:

POST /v1/merchants
curl -X POST https://your-host/v1/merchants \
  -H "Content-Type: application/json" \
  -d '{"name":"My store","webhook_url":"https://my-store.com/ipn"}'

# -> { "merchant_id": "mer_…", "secret_key": "pic_live_…",
#      "publishable_key": "pic_pub_…", "webhook_secret": "whsec_…" }

Save the secret_key and webhook_secret — they are shown only once (the publishable key is always retrievable from GET /v1/account).

Create a payment

POST /v1/invoices
FieldTypeNotes
assetstringCoin to receive, e.g. BTC, USDT.ETH, USDT.TRON. See GET /v1/assets.
amountstringAmount to charge in asset.
order_idstring?Your reference (optional).
settle_asstring?Settle (convert) into this coin instead — uses the 0.5% tier.
curl -X POST https://your-host/v1/invoices \
  -H "Authorization: Bearer pic_live_…" -H "Content-Type: application/json" \
  -d '{"asset":"USDT.ETH","amount":"49.99","order_id":"order-123"}'
{
  "id": "inv_8f3c…",
  "asset": "USDT.ETH",
  "status": "new",
  "pay_address": "0x…",
  "pay_amount": "49.990001",   // exact amount the customer must send
  "amount": "49.99",
  "fee": "0.124975",
  "settle_asset": null,
  "expires_at": 1782579331203
}

Show the customer pay_address and the exact pay_amount — or just redirect them to the hosted checkout.

Hosted checkout

Send the customer to the drop-in payment page (QR, exact amount, live status). No frontend to build:

https://your-host/pay/{invoice_id}

The page polls a public, read-only status endpoint and shows a success screen when confirmed:

GET /pay/{invoice_id}/status (no auth)

Get / list invoices

GET /v1/invoices/{id}
GET /v1/invoices — your 100 most recent
curl https://your-host/v1/invoices/inv_8f3c… -H "Authorization: Bearer pic_live_…"

Status lifecycle

StatusMeaning
newCreated; awaiting payment.
partially_paidSome funds received, below the amount.
confirmingFull amount seen; waiting for confirmations.
paidConfirmed and credited. Terminal.
expiredWindow closed without sufficient payment. Terminal.

Conversion quote

GET /v1/quote?from=BTC&to=USDT.ETH&amount=0.01
{ "from":"BTC", "to":"USDT.ETH", "to_amount":"602.81", "fee_bps":50, "source":"price-oracle" }

Account & balances

GET /v1/account

Returns your payout settings, per-coin balances (with live usd_value), total_usd, webhook URL, and the supported assets list.

GET /v1/balance?asset=BTC
PUT /v1/account { webhook_url?, consolidate_to? }

Payout settings

PUT /v1/account/payouts
FieldNotes
assetCoin these settings apply to.
payout_addressWhere this coin is sent.
modeinstant (auto-pay each payment) or manual (accrue a balance).
instant_min_amountSkip instant payouts below this (so the network fee never exceeds the payout).

Withdraw & sweep

POST /v1/withdrawals { asset, amount?, to_address?, consolidate_to? }
POST /v1/payouts/sweep { to } (consolidate all balances into one coin)
GET /v1/payouts

Payout amounts are before network fees — the blockchain fee is deducted from what is actually sent (net_of_network_fee: false).

Webhooks (IPN)

When an invoice changes status we POST JSON to your webhook_url with these headers:

HeaderValue
x-payincrypto-eventinvoice.paid, invoice.confirming, invoice.partially_paid, invoice.expired
x-payincrypto-signaturesha256=<hmac> of the raw body, keyed with your webhook_secret

Body: { "type": "...", "created_at": 0, "data": { …the invoice… } }. Always verify the signature before trusting it:

import crypto from "node:crypto";

function verify(rawBody, signature, secret) {
  const expected = "sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  const a = Buffer.from(expected), b = Buffer.from(signature || "");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

// in your handler (use the RAW request body, not re-serialized JSON):
if (!verify(rawBody, req.headers["x-payincrypto-signature"], WEBHOOK_SECRET)) {
  return res.status(400).end();
}

Delivery retries with exponential backoff until your endpoint returns 2xx.

Errors

Errors return a JSON body { "error": "code", "message": "…" } with a matching HTTP status:

StatusWhen
400Validation, unsupported asset, invalid amount.
401Missing or invalid API key / session.
404Resource not found.
503A dependency (price oracle, address generation) is temporarily unavailable.

Need something here that's missing? Everything the dashboard does is a public API call — open your browser dev tools on the dashboard to see live examples.