Quick Start
From a blank account to a confirmed test payment in about 10 minutes. No real funds needed.
This guide walks you through the actual dashboard flow merchants follow today. Test mode is fully sandboxed: a pk_test_* key cannot touch mainnet. New accounts get $100 test credit and $50 live credit automatically, which covers your first integration without any top-up.
1. Create your account
/auth/login and switch to the Create account tab.Enter your work email and a password (minimum 12 characters). Click Create account. Or click Continue with Google to sign up via Google OAuth.
For email/password signup, check your inbox for the verification email and click the link. Email verification is required before you can sign in with email and password at all. Google OAuth signups are verified automatically.
Sign in from /auth/login. The dashboard lands you on /dashboard in live mode by default.
2. Switch to test mode
Toggle the mode switch at the bottom of the sidebar to Test (on mobile, open the navigation menu - the switch is at the bottom of the drawer). Live and test modes are isolated: a pk_test_* key cannot create live payments, and vice versa. The toggle is saved to your account and re-applied on every sign-in, from any browser or device.
3. Add a wallet
API keys cannot be created until at least one wallet exists in the current mode. The API key creation page otherwise shows an empty state: "No test wallets yet" when your wallets are all in live mode, or "Add a wallet before creating an API key" when you have no wallets at all.
Not sure which wallet to use for each chain? See Choosing your wallets.
Pick a testnet chain. Two simple options:
- ETH_SEPOLIA: paste a
0x...receive address from MetaMask connected to Sepolia. The mode picker defaults to the per-payment CREATE2 forwarder (badged Recommended). Your pasted address becomes the sweep treasury, and a short one-time on-chain onboarding (attest and register treasury signatures) runs after the wallet is added. - BTC_TESTNET: paste a
vpub5...BIP-84 account key from Sparrow on testnet4.
The form validates the address inline. EVM pastes show one of: EIP-55 verified, Canonicalized, EIP-55 applied, Duplicate of #N, or Checksum mismatch. UTXO pastes are rejected on wrong-variant keys.
4. Create a test API key
Click Create API key. The form heading reads Create API key (the key's mode follows the dashboard toggle).
Fill in a Label (shown in logs, e.g. my-first-test). The form auto-selects every chain and
token your wallets cover. Deselect what you do not want this key to accept.
Optional: scroll to the Webhook endpoint card and paste an HTTPS URL plus the events you want to receive. webhook.site works for quick testing. You can also add a webhook later from the key's expand panel on the API Keys list.
Click Create key. The success page shows your API key (and Webhook secret if you added one). Both are shown only once. Copy them and click I've saved these.
The key starts with pk_test_. Store it server-side, never in browser code.
5. Create a test payment
Call POST /api/v1/payments with your test API key:
POST /api/v1/payments
Authorization: Bearer pk_test_...
Content-Type: application/json
{
"fiat_amount": "10.00",
"fiat_currency": "USD",
"accepted_chains": ["ETH_SEPOLIA"]
}Required fields:
| Field | Type | Notes |
|---|---|---|
fiat_amount | string | Decimal string, e.g. "10.00". Never a number (preserves precision). |
fiat_currency | string | ISO 4217 code, e.g. "USD", "EUR". |
Optional fields: accepted_chains (array), accepted_coins (array), accepted_tokens (array of CHAIN:SYMBOL), restrict_to_accepted (boolean, default false), order_id, description (200 char max), customer_email, success_url, cancel_url.
accepted_chains / accepted_coins / accepted_tokens set the checkout default coin (which coin the checkout opens on). By default they are not a restriction: the customer can switch to any coin your API key accepts. Omit them and the checkout shows the full picker. To limit which coins you receive, set the allowlist on the API key.
Set restrict_to_accepted: true to turn the accepted set into a hard restriction for that one payment: the checkout then offers only the coins/chains in the set, and select-chain rejects anything outside it with 422 COIN_NOT_IN_ACCEPTED_SET. restrict_to_accepted: true with no accepted set returns 422 RESTRICT_TO_ACCEPTED_REQUIRES_ALLOWLIST. Use it to accept a specific coin group (for example stablecoins only) with a single API key instead of one key per group.
Response (201 Created):
{
"data": {
"id": "018f7a3e-12b4-4a8d-bc5e-9f234567890a",
"status": "pending",
"fiat_amount": "10.00",
"fiat_currency": "USD",
"checkout_url": "https://checkout.noholdpay.com/018f7a3e-12b4-4a8d-bc5e-9f234567890a",
"expires_at": "2026-05-14T12:20:00Z",
"created_at": "2026-05-14T12:00:00Z",
"chain_options": [
{
"chain_id": "ETH_SEPOLIA",
"coin_symbol": "ETH",
"crypto_amount": "3456789012345678",
"display_amount": "0.003456",
"address": "",
"payment_uri": "",
"exchange_rate": "2892.45",
"exchange_rate_locked_at": "2026-05-14T12:00:00Z"
}
]
}
}address and payment_uri are empty at creation. The deposit address is derived lazily when the customer picks a chain on the checkout page. Server integrations that need the address up-front can call POST /api/v1/checkout/{id}/select-chain and then GET /api/v1/checkout/{id}.
| Field | Notes |
|---|---|
id | UUIDv4 of the payment. |
status | pending at creation. Transitions to awaiting_payment once the customer picks a chain on the checkout. |
checkout_url | Hand this to the customer. They open it, confirm or switch the coin, and pay. |
chain_options | One entry per chain+coin option the payment can be paid in: every pair your API key accepts, or only the accepted set when restrict_to_accepted is true. crypto_amount is in the coin's smallest unit (wei, satoshi, lamport). display_amount is the human-readable version. |
expires_at | Payment window deadline. See Payment Lifecycle. |
Open checkout_url in a browser to see the hosted checkout.
6. Pay the test invoice
Pay with testnet funds:
- ETH_SEPOLIA: send Sepolia ETH or Sepolia USDC from MetaMask.
- ETH: ghostchain faucet (no auth), Chainlink (requires ~1 LINK).
- USDC: Circle faucet (10 USDC per request).
- BTC_TESTNET: send testnet4 BTC. Faucets: coinfaucet.eu, testnet.help, mempool.space (GitHub auth).
The checkout updates in real time as the transaction is detected and confirmed.
7. Verify the webhook
If you added a webhook in step 4, your endpoint receives a signed payment.confirmed event after the required confirmations.
Go to Webhooks in the sidebar. The page shows every delivery attempt with status code, latency, and event name.
A failing endpoint retries on the standard schedule (10s, 30s, 2m, 10m, 30m, 2h, 8h, 24h with +/-25% jitter - 9 total attempts over about 35 hours). Use Send test on the endpoint card to fire a synthetic event.
See Webhooks for signature verification.
Webhook URLs are managed on the API Keys page, not the Webhooks page. The Webhooks page is a read-only delivery inspector. To add, edit, or remove a webhook, expand the relevant API key on /api-keys.
Test vs live
| Test | Live | |
|---|---|---|
| API key prefix | pk_test_ | pk_live_ |
| Chains | Testnets only | Mainnets only |
| Real funds | No | Yes |
| Prepaid Credit | Required (separate test reserve) | Required (separate live reserve) |
| Signup grant | $100 test credit | $50 live credit |
Both modes deduct platform fees from a Prepaid Credit reserve. The reserves are separate. New accounts get both grants automatically at signup, so no top-up is needed to start integrating.
Switch to live by toggling the mode switch at the bottom of the sidebar, creating a live wallet, and issuing a pk_live_* key. The API calls are identical.
Next steps
- Payment lifecycle. Every status and what each means.
- Webhooks. Signature verification, retry handling, full event list.
- Trust model. What NoHoldPay can and cannot do with your funds.
- Pick your chain: Bitcoin, Ethereum and EVM, Solana, TRON, XRP, Monero.