NOWPayments Ruby SDK
Production-ready Ruby wrapper for the NOWPayments API. Accept cryptocurrency payments with minimal code.
Why NOWPayments?
- 150+ cryptocurrencies - Bitcoin, Ethereum, USDT, and more
- No KYC required - Accept payments immediately
- Instant settlement - Real-time payment processing
- Low fees - Competitive transaction costs
- Global reach - Accept payments from anywhere
Installation
Add to your Gemfile:
gem 'nowpayments', git: 'https://github.com/Sentia/nowpayments'Or install directly:
gem install nowpaymentsQuick Start
require 'nowpayments'
# Initialize client (sandbox for testing, production when ready)
client = NOWPayments::Client.new(
api_key: ENV['NOWPAYMENTS_API_KEY'],
sandbox: true
)
# Create a payment
payment = client.create_payment(
price_amount: 100.0,
price_currency: 'usd',
pay_currency: 'btc',
order_id: 'order-123',
ipn_callback_url: 'https://yourdomain.com/webhooks/nowpayments'
)
puts "Payment address: #{payment['pay_address']}"
puts "Amount: #{payment['pay_amount']} BTC"
puts "Status: #{payment['payment_status']}"Features
🎉 Complete API Coverage - 57 Methods, 100% Coverage!
11 API Modules with Full Implementation:
- Authentication (5 methods) - JWT token management for protected endpoints
- Status (1 method) - API health checks
- Currencies (3 methods) - Available cryptocurrencies and details
- Payments (4 methods) - Create and track cryptocurrency payments
- Invoices (3 methods) - Hosted payment pages with status tracking
- Estimates (2 methods) - Price calculations and minimum amounts
- Mass Payouts (8 methods) - Batch withdrawals with 2FA verification
- Conversions (3 methods) - Currency conversions at market rates
- Subscriptions (9 methods) - Recurring payment plans and billing
- Custody/Sub-accounts (11 methods) - User wallet management for marketplaces
- Fiat Payouts (8 methods) - Beta: Crypto to fiat withdrawals
Security & Production Ready:
- JWT Authentication - Bearer token support for sensitive operations
- Webhook Verification - HMAC-SHA512 signature validation
- Constant-time comparison - Prevents timing attacks
- Comprehensive error handling - 8 exception classes with detailed messages
- 100% tested - 23 passing tests, RuboCop clean
Complete Method List (57 Methods)
-
authenticate(email:, password:)- Get JWT token (5-min expiry) -
jwt_token(email:, password:)- Get token with auto-refresh -
jwt_expired?- Check if token is expired -
clear_jwt_token- Clear stored token -
jwt_time_remaining- Seconds until expiry
-
status- Check API status -
currencies(fixed_rate: nil)- Get available currencies -
full_currencies- Detailed currency information -
merchant_coins- Your enabled currencies
-
create_payment(...)- Create new payment -
payment(payment_id)- Get payment status -
payments(limit:, page:, ...)- List payments with filters -
update_payment_estimate(payment_id)- Update exchange rate
-
create_invoice(...)- Create invoice with payment page -
create_invoice_payment(...)- Create payment by invoice ID -
invoice(invoice_id)- Get invoice status
-
estimate(amount:, currency_from:, currency_to:)- Price estimate -
min_amount(currency_from:, currency_to:)- Minimum payment amount
-
balance- Get account balance -
create_payout(withdrawals:, ...)- Create batch payout (JWT) -
verify_payout(batch_withdrawal_id:, verification_code:)- 2FA verify (JWT) -
payout_status(payout_id)- Get payout status -
list_payouts(limit:, offset:)- List all payouts (JWT) -
validate_payout_address(address:, currency:, ...)- Validate address -
min_payout_amount(currency:)- Minimum payout amount -
payout_fee(currency:, amount:)- Calculate payout fee
-
create_conversion(from_currency:, to_currency:, amount:)- Convert crypto (JWT) -
conversion_status(conversion_id)- Check conversion status (JWT) -
list_conversions(limit:, offset:)- List all conversions (JWT)
-
subscription_plans- List all subscription plans -
create_subscription_plan(plan_data)- Create new plan -
update_subscription_plan(plan_id, plan_data)- Update plan -
subscription_plan(plan_id)- Get plan details -
create_subscription(plan_id:, email:)- Create subscription -
list_recurring_payments(...)- List recurring payments with filters -
recurring_payment(subscription_id)- Get subscription details -
delete_recurring_payment(subscription_id)- Cancel subscription (JWT) -
subscription_payments(subscription_id)- List subscription payments
-
create_sub_account(user_id:)- Create user account -
sub_account_balance(user_id)- Get user balance -
sub_account_balances- Get all balances -
list_sub_accounts(...)- List all sub-accounts -
transfer_between_sub_accounts(...)- Transfer between users (JWT) -
create_sub_account_deposit(user_id:, currency:, ...)- Generate deposit address -
create_sub_account_payment_deposit(...)- Payment to sub-account -
transfer_to_sub_account(user_id:, currency:, amount:)- Deposit to user -
withdraw_from_sub_account(user_id:, currency:, amount:)- Withdraw from user (JWT) -
sub_account_transfer(transfer_id)- Get transfer details -
sub_account_transfers(...)- List all transfers
-
fiat_payout_payment_methods(fiat_currency: nil)- Available payment methods (JWT) -
create_fiat_payout_account(...)- Create payout account (JWT) -
fiat_payout_accounts(...)- List payout accounts (JWT) -
update_fiat_payout_account(account_id:, ...)- Update account (JWT) -
create_fiat_payout(...)- Create fiat payout (JWT) -
fiat_payout_status(payout_id)- Get payout status (JWT) -
fiat_payouts(...)- List all fiat payouts with filters (JWT) -
fiat_payout_rates(...)- Get conversion rates (JWT)
Built for Production
- Comprehensive error handling - 8 exception classes with detailed messages
- Faraday middleware - Automatic error mapping and retries
- Tested - 23 passing tests with VCR cassettes for integration
- Rails-ready - Drop-in Rack middleware for webhook verification
- Type-safe - All responses return Ruby Hashes from parsed JSON
Usage Examples
Accept Payment on Your Site
# 1. Create payment
payment = client.create_payment(
price_amount: 49.99,
price_currency: 'usd',
pay_currency: 'btc',
order_id: "order-#{order.id}",
order_description: 'Pro Plan - Annual',
ipn_callback_url: 'https://example.com/webhooks/nowpayments'
)
# 2. Show payment address to customer
@payment_address = payment['pay_address']
@payment_amount = payment['pay_amount']
# 3. Check status
status = client.payment(payment['payment_id'])
# => {"payment_status"=>"finished", ...}Hosted Invoice Page
# Create invoice with hosted payment page
invoice = client.create_invoice(
price_amount: 99.0,
price_currency: 'usd',
order_id: "inv-#{invoice.id}",
success_url: 'https://example.com/thank-you',
cancel_url: 'https://example.com/checkout'
)
# Redirect customer to payment page
redirect_to invoice['invoice_url']
# Customer can choose from 150+ cryptocurrenciesCustody API - Sub-accounts (Marketplaces & Casinos)
# Create sub-account for a user
sub_account = client.create_sub_account(user_id: user.id)
# => {"id"=>123, "user_id"=>456, "created_at"=>"2025-11-01T..."}
# Generate deposit address for user's BTC wallet
deposit = client.create_sub_account_deposit(
user_id: user.id,
currency: 'btc'
)
# => {"address"=>"bc1q...", "currency"=>"btc"}
# Check user's balance
balances = client.sub_account_balances(user_id: user.id)
# => {"balances"=>{"btc"=>0.05, "eth"=>1.2}}
# Transfer funds to sub-account
transfer = client.transfer_to_sub_account(
user_id: user.id,
currency: 'btc',
amount: 0.01
)
# Process withdrawal
withdrawal = client.withdraw_from_sub_account(
user_id: user.id,
currency: 'btc',
amount: 0.005
)JWT Authentication (Required for Advanced Features)
Some endpoints require JWT authentication (expires every 5 minutes):
# Authenticate to get JWT token
client.authenticate(
email: 'your_email@example.com',
password: 'your_password'
)
# Token is automatically stored and injected in subsequent requests
# Check token status
client.jwt_expired? # => false
client.jwt_time_remaining # => 287 (seconds)
# JWT is required for these endpoints:
# - Mass Payouts (create_payout, verify_payout, list_payouts)
# - Conversions (create_conversion, conversion_status, list_conversions)
# - Custody Operations (transfer_between_sub_accounts, write_off_sub_account_balance)
# - Recurring Payments (delete_recurring_payment)
# Example: Create payout (requires JWT)
client.authenticate(email: 'your@email.com', password: 'password')
payout = client.create_payout(
withdrawals: [
{
address: 'TEmGwPeRTPiLFLVfBxXkSP91yc5GMNQhfS',
currency: 'trx',
amount: 10
}
],
payout_description: 'Weekly payouts'
)
# Verify payout with 2FA code (from Google Authenticator)
client.verify_payout(
batch_withdrawal_id: payout['id'],
verification_code: '123456'
)
# Token auto-refresh pattern
def ensure_authenticated(client, email, password)
return unless client.jwt_expired?
client.authenticate(email: email, password: password)
end
# Before JWT-required operations
ensure_authenticated(client, EMAIL, PASSWORD)
payouts = client.list_payouts(limit: 10, offset: 0)
# Clear token when done (optional, for security)
client.clear_jwt_tokenSee examples/jwt_authentication_example.rb for complete usage patterns.
Currency Conversions (JWT Required)
Convert between cryptocurrencies at market rates:
# 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" => "conv_123", "status" => "processing", ...}
# Check conversion status
status = client.conversion_status(conversion['conversion_id'])
# => {"status" => "completed", "from_amount" => 0.1, "to_amount" => 2.5, ...}
# List all conversions
conversions = client.list_conversions(limit: 10, offset: 0)Fiat Payouts (Beta - JWT Required)
Withdraw cryptocurrency to fiat bank accounts:
# Authenticate first
client.authenticate(email: 'your@email.com', password: 'password')
# Get available payment methods
methods = client.fiat_payout_payment_methods(fiat_currency: 'EUR')
# => {"result" => [{"provider" => "transfi", "methods" => [...]}]}
# Create payout account
account = client.create_fiat_payout_account(
provider: 'transfi',
fiat_currency: 'EUR',
account_data: {
accountHolderName: 'John Doe',
iban: 'DE89370400440532013000'
}
)
# => {"result" => {"id" => "acc_123", ...}}
# Get conversion rates
rates = client.fiat_payout_rates(
crypto_currency: 'btc',
fiat_currency: 'EUR',
crypto_amount: 0.1
)
# => {"result" => {"fiatAmount" => "2500.00", "rate" => "25000.00", ...}}
# Create fiat payout
payout = client.create_fiat_payout(
account_id: account['result']['id'],
crypto_currency: 'btc',
crypto_amount: 0.1
)
# => {"result" => {"id" => "payout_123", "status" => "PENDING", ...}}
# Check payout status
status = client.fiat_payout_status(payout['result']['id'])
# => {"result" => {"status" => "FINISHED", ...}}
# List all fiat payouts with filters
payouts = client.fiat_payouts(
status: 'FINISHED',
fiat_currency: 'EUR',
limit: 10,
page: 0
)Webhook Verification (Critical!)
Always verify webhook signatures to prevent fraud:
# app/controllers/webhooks_controller.rb
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 status
order = Order.find_by(id: payload['order_id'])
case payload['payment_status']
when 'finished'
order.mark_paid!
OrderMailer.payment_received(order).deliver_later
when 'failed', 'expired'
order.cancel!
when 'partially_paid'
# Customer sent wrong amount
logger.warn "Underpaid: #{payload['actually_paid']} vs #{payload['pay_amount']}"
end
head :ok
rescue NOWPayments::SecurityError => e
logger.error "Invalid webhook signature: #{e.message}"
head :forbidden
end
end
# config/routes.rb
post '/webhooks/nowpayments', to: 'webhooks#nowpayments'Error Handling
begin
payment = client.create_payment(...)
rescue NOWPayments::AuthenticationError
# Invalid API key
rescue NOWPayments::BadRequestError => e
# Invalid parameters
puts "Error: #{e.message}"
puts "Details: #{e.body}"
rescue NOWPayments::RateLimitError => e
# Too many requests
retry_after = e.headers['Retry-After']
rescue NOWPayments::ServerError
# NOWPayments server error
rescue NOWPayments::ConnectionError
# Network error
endDocumentation
- Complete API Reference - All methods with examples
- Official API Docs - NOWPayments API documentation
- Dashboard - Production environment
- Sandbox Dashboard - Testing environment
Testing with Sandbox
# Use sandbox for development
client = NOWPayments::Client.new(
api_key: ENV['NOWPAYMENTS_SANDBOX_API_KEY'],
ipn_secret: ENV['NOWPAYMENTS_SANDBOX_IPN_SECRET'],
sandbox: true
)
# All API calls go to sandbox environment
payment = client.create_payment(...)Get sandbox credentials:
- Create account at https://account-sandbox.nowpayments.io/
- Generate API key from dashboard
- Generate IPN secret for webhooks
- Add to
.envfile
Configuration
# .env
NOWPAYMENTS_API_KEY=your_production_api_key
NOWPAYMENTS_IPN_SECRET=your_ipn_secret
# Testing
NOWPAYMENTS_SANDBOX_API_KEY=your_sandbox_api_key
NOWPAYMENTS_SANDBOX_IPN_SECRET=your_sandbox_ipn_secretExamples
See the examples/ directory:
# API usage demo
cp .env.example .env
# Add your sandbox credentials to .env
ruby examples/simple_demo.rb
# Webhook receiver (Sinatra)
ruby examples/webhook_server.rb
# Use ngrok to expose: ngrok http 4567Development
# Install dependencies
bundle install
# Run tests
bundle exec rspec
# Run tests with coverage
COVERAGE=true bundle exec rspec
# Lint code
bundle exec rubocop
# Interactive console
bundle exec rake consoleContributing
- Fork it
- Create your feature branch (
git checkout -b feature/my-feature) - Run tests (
bundle exec rspec) - Commit your changes (
git commit -am 'Add feature') - Push to the branch (
git push origin feature/my-feature) - Create a Pull Request
Security
Report security vulnerabilities to: security@yourdomain.com
Never commit API keys or secrets. Always use environment variables.
License
MIT License - see LICENSE.txt