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. Your initial business receives configured test welcome credit at signup, when enabled. Live usage requires funding. An eligible account can receive the current first-top-up promotional match after its first qualifying confirmed live top-up and email verification. Check the Prepaid Credit page for the match percentage, minimum deposit, cap, and offer currency.
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 and receive a first-top-up promotional match. Google and GitHub OAuth signups already have a verified email, but still need a qualifying live top-up for the promotion.
Sign in from /auth/login. The dashboard lands you on /dashboard in live mode by default.
If sign-up is refused
New registrations by email and password, Google, or GitHub are checked before the account is created, and a refused registration creates nothing. These checks never apply to signing in to an existing account or to linking Google or GitHub to an account you already have. The sign-up form shows what to do next.
New registrations also check the mail servers published for the email domain. A temporary DNS problem does not block registration. Registrations from hosting, datacenter, VPN or Tor networks have stricter limits than home and mobile networks, so if you are refused while connected through one, try again from your usual connection. The codes are:
| Code | HTTP | When |
|---|---|---|
DISPOSABLE_EMAIL_NOT_ALLOWED | 422 | The address belongs to a temporary or disposable email provider. Use a permanent address. |
EMAIL_DOMAIN_NOT_ALLOWED | 422 | The email domain, or a parent domain, is not accepted for new registrations. Use a different address. |
EMAIL_SUBDOMAIN_NOT_ALLOWED | 422 | The address is at an email subdomain, such as you@mail.example.com. Use an address at the main domain, such as you@example.com. |
EMAIL_DOMAIN_NO_MAIL_SERVER | 422 | This email domain cannot receive email. Check the address or use a different email address. |
EMAIL_DOMAIN_RATE_LIMITED | 429 | Too many new accounts use this email domain today. Please try again later or contact support. The Retry-After header gives the seconds to wait. |
SIGNUP_RATE_LIMITED | 429 | Too many new accounts were registered from your network recently. The Retry-After response header gives the seconds to wait. |
SIGNUP_IP_BLOCKED | 403 | New registrations are not accepted from your network. |
SIGNUP_SOURCE_UNVERIFIED | 403 | Your network address could not be verified. Try again later. |
REGISTRATION_UNAVAILABLE | 403 | Registration could not be completed. Contact support. |
The daily limit per email domain counts only accounts whose address is confirmed: a Google or GitHub registration counts at once, and an email and password registration counts when you verify the address. If the limit fills up before you verify, verification is refused with EMAIL_DOMAIN_RATE_LIMITED as well. Your verification link or code is not used up by that refusal, so try again after the Retry-After wait, and request a new code if it has expired by then.
If you think a refusal is a mistake, contact support from the email address you want to register.
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 with a test-mode key. Live keys refuse public request inspection services, so point a live webhook at a receiver you control. See Webhook URL rules. 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) |
| Introductory credit | Configured test amount at signup | First qualifying live top-up match after verification, when eligible |
Both modes deduct platform fees and platform-charged network costs from their own Prepaid Credit reserve. The initial business receives configured test welcome credit at signup, when enabled. Live credit comes from confirmed funding and any eligible first-top-up match. The promotion is available once across linked businesses, requires verified email, and is not refundable or transferable. Additional businesses start with no signup grant. See Prepaid Credit for offer and refund rules.
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
- Multiple businesses. Keep brands, wallets, API keys, and prepaid credit separate under one login.
- 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.