Complete API documentation with all available methods and usage examples.
- Authentication
- Status API
- Payments API
- Invoices API
- Estimates API
- Recurring Payments API
- Mass Payouts API
- Conversions API
- Custody API
- Error Handling
require 'nowpayments'
# Production
client = NOWPayments::Client.new(
api_key: ENV['NOWPAYMENTS_API_KEY'],
ipn_secret: ENV['NOWPAYMENTS_IPN_SECRET'] # Optional, for webhook verification
)
# Sandbox (for testing)
client = NOWPayments::Client.new(
api_key: ENV['NOWPAYMENTS_SANDBOX_API_KEY'],
ipn_secret: ENV['NOWPAYMENTS_SANDBOX_IPN_SECRET'],
sandbox: true
)Some endpoints require JWT authentication (token expires after 5 minutes):
Required for:
- Mass Payouts (create, verify, list)
- Conversions (all endpoints)
- Custody Operations (transfers, write-offs)
- Recurring Payments (delete)
Authenticate and obtain a JWT token (valid for 5 minutes).
response = client.authenticate(
email: 'your_email@example.com',
password: 'your_password'
)
# => {"token" => "eyJhbGc..."}
# Token is automatically stored and injected in subsequent requestsGet current JWT token, optionally refreshing if expired.
# Get current token without refresh
token = client.jwt_token
# => "eyJhbGc..." or nil if expired/not authenticated
# Get token with auto-refresh if expired
token = client.jwt_token(
email: 'your_email@example.com',
password: 'your_password'
)
# => "eyJhbGc..."Check if JWT token is expired or missing.
client.jwt_expired? # => falseGet seconds until JWT token expires.
client.jwt_time_remaining # => 287Manually clear stored JWT token (optional, for security).
client.clear_jwt_token
client.jwt_expired? # => trueAuto-refresh pattern:
EMAIL = 'your_email@example.com'
PASSWORD = 'your_password'
def ensure_authenticated(client, email, password)
return unless client.jwt_expired?
client.authenticate(email: email, password: password)
end
# Before each JWT-required operation
ensure_authenticated(client, EMAIL, PASSWORD)
payout = client.create_payout(...)Get API status (uptime, latency).
status = client.status
# => {"message" => "OK"}Get list of available currencies.
currencies = client.currencies
# => {"currencies" => ["btc", "eth", "usdt", ...]}Get detailed currency information including logos, networks, and limits.
currencies = client.currencies_full
# => {"currencies" => [{"currency" => "btc", "logo_url" => "...", "network" => "BTC", ...}, ...]}Get currencies available to your merchant account.
currencies = client.merchant_currencies
# => {"currencies" => ["btc", "eth", ...]}Get currencies you've enabled in your account settings.
currencies = client.selected_currencies
# => {"currencies" => ["btc", "eth", "usdt"]}Create a new payment.
payment = client.create_payment(
price_amount: 100.0, # Amount in price_currency
price_currency: 'usd', # Fiat or crypto
pay_currency: 'btc', # Cryptocurrency customer pays with
order_id: 'order-123', # Your internal order ID
order_description: 'Pro Plan - Annual',
ipn_callback_url: 'https://example.com/webhooks/nowpayments',
success_url: 'https://example.com/success',
cancel_url: 'https://example.com/cancel'
)
# => {
# "payment_id" => "5729887098",
# "payment_status" => "waiting",
# "pay_address" => "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
# "pay_amount" => 0.00123456,
# "pay_currency" => "btc",
# "price_amount" => 100.0,
# "price_currency" => "usd",
# "order_id" => "order-123",
# "expiration_estimate_date" => "2025-11-01T12:30:00.000Z"
# }Get payment status and details.
payment = client.payment('5729887098')
# => {
# "payment_id" => "5729887098",
# "payment_status" => "finished",
# "pay_address" => "bc1q...",
# "pay_amount" => 0.00123456,
# "actually_paid" => 0.00123456,
# "outcome_amount" => 99.5,
# "outcome_currency" => "usd"
# }Payment statuses:
waiting- Waiting for customer to send cryptocurrencyconfirming- Payment received, waiting for confirmationsconfirmed- Payment confirmedsending- Sending to your walletpartially_paid- Customer sent wrong amountfinished- Payment completefailed- Payment failedrefunded- Payment refundedexpired- Payment expired
Get minimum payment amount for a currency pair.
minimum = client.minimum_payment_amount(
currency_from: 'btc',
currency_to: 'eth',
fiat_equivalent: 'usd'
)
# => {
# "currency_from" => "btc",
# "currency_to" => "eth",
# "fiat_equivalent" => "usd",
# "min_amount" => 0.0001
# }Get your account balance.
balance = client.balance
# => {
# "btc" => 0.5,
# "eth" => 10.0,
# "usdt" => 1000.0
# }Create hosted payment pages where customers can choose from 150+ cryptocurrencies.
Create a payment invoice with hosted page.
invoice = client.create_invoice(
price_amount: 99.0,
price_currency: 'usd',
order_id: "inv-#{order.id}",
order_description: 'Pro Plan - Monthly',
success_url: 'https://example.com/thank-you',
cancel_url: 'https://example.com/checkout',
ipn_callback_url: 'https://example.com/webhooks/nowpayments'
)
# => {
# "id" => "5824448584",
# "order_id" => "inv-123",
# "order_description" => "Pro Plan - Monthly",
# "price_amount" => 99.0,
# "price_currency" => "usd",
# "invoice_url" => "https://nowpayments.io/payment/?iid=5824448584",
# "created_at" => "2025-11-01T12:00:00.000Z"
# }
# Redirect customer to invoice_url
redirect_to invoice['invoice_url']Get invoice details and payment status.
invoice = client.invoice('5824448584')
# => {
# "id" => "5824448584",
# "payment_status" => "finished",
# "pay_currency" => "btc",
# "pay_amount" => 0.00234567,
# ...
# }Estimate exchange amount between currencies.
estimate = client.estimate(
amount: 100,
currency_from: 'usd',
currency_to: 'btc'
)
# => {
# "currency_from" => "usd",
# "amount_from" => 100,
# "currency_to" => "btc",
# "estimated_amount" => 0.00234567
# }Create and manage subscription billing.
Create a recurring payment plan.
subscription = client.create_recurring_payment(
price_amount: 29.99,
price_currency: 'usd',
pay_currency: 'btc',
order_id: "sub-#{subscription.id}",
order_description: 'Monthly Subscription',
period: 'month', # 'day', 'week', 'month', 'year'
ipn_callback_url: 'https://example.com/webhooks/nowpayments'
)
# => {
# "id" => "recurring_123",
# "order_id" => "sub-456",
# "price_amount" => 29.99,
# "price_currency" => "usd",
# "pay_currency" => "btc",
# "period" => "month",
# "status" => "active"
# }Get recurring payment details.
subscription = client.recurring_payment('recurring_123')
# => {
# "id" => "recurring_123",
# "status" => "active",
# "next_payment_date" => "2025-12-01T00:00:00.000Z",
# ...
# }Requires JWT authentication.
Cancel a recurring payment.
# Authenticate first
client.authenticate(email: 'your@email.com', password: 'password')
# Delete recurring payment
result = client.delete_recurring_payment('recurring_123')
# => {"result" => true}All payout endpoints require JWT authentication.
Requires JWT authentication.
Create a batch payout to multiple addresses.
# Authenticate first
client.authenticate(email: 'your@email.com', password: 'password')
# Create payout
payout = client.create_payout(
withdrawals: [
{
address: 'TEmGwPeRTPiLFLVfBxXkSP91yc5GMNQhfS',
currency: 'trx',
amount: 10,
extra_id: nil # Required for some currencies (XRP, XLM, etc.)
},
{
address: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb',
currency: 'eth',
amount: 0.1
}
],
payout_description: 'Weekly affiliate payouts'
)
# => {
# "id" => "batch_123",
# "withdrawals" => [
# {
# "id" => "withdrawal_456",
# "address" => "TEmGw...",
# "currency" => "trx",
# "amount" => 10,
# "status" => "pending"
# },
# ...
# ]
# }Requires JWT authentication.
Verify payout with 2FA code (from Google Authenticator or email).
# Authenticate first
client.authenticate(email: 'your@email.com', password: 'password')
# Verify with 2FA code
result = client.verify_payout(
batch_withdrawal_id: 'batch_123',
verification_code: '123456' # From Google Authenticator
)
# => {"result" => true}Requires JWT authentication.
List all payouts.
# Authenticate first
client.authenticate(email: 'your@email.com', password: 'password')
# List payouts
payouts = client.list_payouts(limit: 10, offset: 0)
# => {
# "count" => 5,
# "data" => [
# {
# "id" => "batch_123",
# "status" => "verified",
# "created_at" => "2025-11-01T12:00:00.000Z",
# ...
# },
# ...
# ]
# }All conversion endpoints require JWT authentication.
Convert between cryptocurrencies at market rates.
Requires JWT authentication.
Create a currency conversion.
# Authenticate first
client.authenticate(email: 'your@email.com', password: 'password')
# Create conversion
conversion = client.create_conversion(
from_currency: 'btc',
to_currency: 'eth',
amount: 0.1
)
# => {
# "conversion_id" => "conversion_123",
# "from_currency" => "btc",
# "to_currency" => "eth",
# "from_amount" => 0.1,
# "to_amount" => 2.5,
# "status" => "processing"
# }Requires JWT authentication.
Check conversion status.
# Authenticate first
client.authenticate(email: 'your@email.com', password: 'password')
# Check status
status = client.conversion_status('conversion_123')
# => {
# "conversion_id" => "conversion_123",
# "status" => "completed",
# "from_currency" => "btc",
# "to_currency" => "eth",
# "from_amount" => 0.1,
# "to_amount" => 2.5
# }Requires JWT authentication.
List all conversions.
# Authenticate first
client.authenticate(email: 'your@email.com', password: 'password')
# List conversions
conversions = client.list_conversions(limit: 10, offset: 0)
# => {
# "count" => 3,
# "data" => [
# {
# "conversion_id" => "conversion_123",
# "status" => "completed",
# "created_at" => "2025-11-01T12:00:00.000Z",
# ...
# },
# ...
# ]
# }Manage sub-accounts for users (marketplaces, casinos, exchanges).
Create a sub-account for a user.
sub_account = client.create_sub_account(user_id: 'user_123')
# => {
# "result" => {
# "id" => 123456,
# "user_id" => "user_123",
# "created_at" => "2025-11-01T12:00:00.000Z"
# }
# }Generate deposit address for user's wallet.
deposit = client.create_sub_account_deposit(
user_id: 'user_123',
currency: 'btc'
)
# => {
# "result" => {
# "user_id" => "user_123",
# "currency" => "btc",
# "address" => "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh"
# }
# }Get user's sub-account balance.
balances = client.sub_account_balances(user_id: 'user_123')
# => {
# "result" => {
# "balances" => {
# "btc" => 0.5,
# "eth" => 10.0,
# "usdt" => 1000.0
# }
# }
# }Transfer funds from main account to sub-account.
transfer = client.transfer_to_sub_account(
user_id: 'user_123',
currency: 'btc',
amount: 0.1
)
# => {
# "result" => {
# "id" => "transfer_456",
# "user_id" => "user_123",
# "currency" => "btc",
# "amount" => 0.1,
# "status" => "completed"
# }
# }Requires JWT authentication.
Transfer between two sub-accounts.
# Authenticate first
client.authenticate(email: 'your@email.com', password: 'password')
# Transfer between users
transfer = client.transfer_between_sub_accounts(
currency: 'btc',
amount: 0.05,
from_id: 'user_123',
to_id: 'user_456'
)
# => {
# "result" => {
# "id" => "transfer_789",
# "from_id" => "user_123",
# "to_id" => "user_456",
# "currency" => "btc",
# "amount" => 0.05,
# "status" => "completed"
# }
# }Withdraw funds from sub-account to external address.
withdrawal = client.withdraw_from_sub_account(
user_id: 'user_123',
currency: 'btc',
amount: 0.05,
address: 'bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh'
)
# => {
# "result" => {
# "id" => "withdrawal_789",
# "user_id" => "user_123",
# "currency" => "btc",
# "amount" => 0.05,
# "address" => "bc1q...",
# "status" => "processing"
# }
# }Requires JWT authentication.
Write off (deduct) balance from sub-account.
# Authenticate first
client.authenticate(email: 'your@email.com', password: 'password')
# Write off balance
result = client.write_off_sub_account_balance(
user_id: 'user_123',
currency: 'btc',
amount: 0.01,
external_id: 'fee_charge_789'
)
# => {
# "result" => {
# "user_id" => "user_123",
# "currency" => "btc",
# "amount" => 0.01,
# "external_id" => "fee_charge_789",
# "status" => "completed"
# }
# }The SDK raises specific exceptions for different error types:
begin
payment = client.create_payment(...)
rescue NOWPayments::AuthenticationError => e
# 401 - Invalid API key
puts "Authentication failed: #{e.message}"
rescue NOWPayments::BadRequestError => e
# 400 - Invalid parameters
puts "Bad request: #{e.message}"
puts "Details: #{e.body}"
rescue NOWPayments::NotFoundError => e
# 404 - Resource not found
puts "Not found: #{e.message}"
rescue NOWPayments::UnprocessableEntityError => e
# 422 - Validation errors
puts "Validation failed: #{e.message}"
rescue NOWPayments::RateLimitError => e
# 429 - Too many requests
retry_after = e.headers['Retry-After']
puts "Rate limited. Retry after #{retry_after} seconds"
rescue NOWPayments::ServerError => e
# 500 - NOWPayments server error
puts "Server error: #{e.message}"
rescue NOWPayments::SecurityError => e
# Webhook signature verification failed
puts "Security error: #{e.message}"
rescue NOWPayments::ConnectionError => e
# Network/connection error
puts "Connection error: #{e.message}"
rescue NOWPayments::Error => e
# Generic API error
puts "API error: #{e.message}"
endException hierarchy:
NOWPayments::Error
├── NOWPayments::AuthenticationError (401)
├── NOWPayments::BadRequestError (400)
├── NOWPayments::NotFoundError (404)
├── NOWPayments::UnprocessableEntityError (422)
├── NOWPayments::RateLimitError (429)
├── NOWPayments::ServerError (5xx)
├── NOWPayments::SecurityError (webhook verification)
└── NOWPayments::ConnectionError (network)
Critical: Always verify webhook signatures to prevent fraud.
# Rails controller
class WebhooksController < ApplicationController
skip_before_action :verify_authenticity_token
def nowpayments
# Verify signature - raises SecurityError if invalid
payload = NOWPayments::Rack.verify_webhook(
request,
ENV['NOWPAYMENTS_IPN_SECRET']
)
# Process payment
order = Order.find_by(id: payload['order_id'])
case payload['payment_status']
when 'finished'
order.mark_paid!
when 'failed', 'expired'
order.cancel!
end
head :ok
rescue NOWPayments::SecurityError => e
logger.error "Invalid webhook: #{e.message}"
head :forbidden
end
endSignature verification (low-level):
require 'openssl'
def verify_signature(payload, signature, secret)
hmac = OpenSSL::HMAC.hexdigest('SHA512', secret, payload)
# Use constant-time comparison to prevent timing attacks
secure_compare(hmac, signature)
end
def secure_compare(a, b)
return false unless a.bytesize == b.bytesize
l = a.unpack("C*")
r = 0
i = -1
b.each_byte { |byte| r |= byte ^ l[i += 1] }
r == 0
endStandard limits:
- 60 requests per minute (standard endpoints)
- 10 requests per minute (payment creation)
Best practices:
- Implement exponential backoff on rate limit errors
- Cache currency lists and static data
- Use batch operations (payouts, invoices) when possible
- Monitor
Retry-Afterheader in rate limit responses
def with_retry(max_retries: 3)
retries = 0
begin
yield
rescue NOWPayments::RateLimitError => e
retries += 1
if retries <= max_retries
retry_after = e.headers['Retry-After'].to_i
sleep retry_after
retry
else
raise
end
end
end
# Usage
with_retry do
client.create_payment(...)
endUse sandbox for development:
client = NOWPayments::Client.new(
api_key: ENV['NOWPAYMENTS_SANDBOX_API_KEY'],
sandbox: true
)Get sandbox credentials:
- Create account at https://account-sandbox.nowpayments.io/
- Generate API key from dashboard
- Generate IPN secret for webhooks
- Test with sandbox cryptocurrencies
Sandbox test currencies:
- All major cryptocurrencies available
- Instant confirmations (no waiting)
- Test payouts without real funds
- Test webhooks with ngrok