NOWPayments Ruby SDK

Gem Version License: MIT

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 nowpayments

Quick 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:

  1. Authentication (5 methods) - JWT token management for protected endpoints
  2. Status (1 method) - API health checks
  3. Currencies (3 methods) - Available cryptocurrencies and details
  4. Payments (4 methods) - Create and track cryptocurrency payments
  5. Invoices (3 methods) - Hosted payment pages with status tracking
  6. Estimates (2 methods) - Price calculations and minimum amounts
  7. Mass Payouts (8 methods) - Batch withdrawals with 2FA verification
  8. Conversions (3 methods) - Currency conversions at market rates
  9. Subscriptions (9 methods) - Recurring payment plans and billing
  10. Custody/Sub-accounts (11 methods) - User wallet management for marketplaces
  11. 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)

Authentication (5 methods) - JWT token management - `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 & Currencies (4 methods) - API health and currency info - `status` - Check API status - `currencies(fixed_rate: nil)` - Get available currencies - `full_currencies` - Detailed currency information - `merchant_coins` - Your enabled currencies
Payments (4 methods) - Standard cryptocurrency payments - `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
Invoices (3 methods) - Hosted payment pages - `create_invoice(...)` - Create invoice with payment page - `create_invoice_payment(...)` - Create payment by invoice ID - `invoice(invoice_id)` - Get invoice status
Estimates (2 methods) - Price calculations - `estimate(amount:, currency_from:, currency_to:)` - Price estimate - `min_amount(currency_from:, currency_to:)` - Minimum payment amount
Mass Payouts (8 methods) - Batch withdrawals (JWT required) - `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
Conversions (3 methods) - Currency conversions (JWT required) - `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)
Subscriptions (9 methods) - Recurring payments - `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
Custody/Sub-accounts (11 methods) - User wallet management - `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 Payouts (8 methods) - Beta: Crypto to fiat (JWT required) - `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+ cryptocurrencies

Custody API - Sub-accounts (Marketplaces & Casinos)

# Create sub-account for a user
 = client.(user_id: user.id)
# => {"id"=>123, "user_id"=>456, "created_at"=>"2025-11-01T..."}

# Generate deposit address for user's BTC wallet
deposit = client.(
  user_id: user.id,
  currency: 'btc'
)
# => {"address"=>"bc1q...", "currency"=>"btc"}

# Check user's balance
balances = client.(user_id: user.id)
# => {"balances"=>{"btc"=>0.05, "eth"=>1.2}}

# Transfer funds to sub-account
transfer = client.(
  user_id: user.id,
  currency: 'btc',
  amount: 0.01
)

# Process withdrawal
withdrawal = client.(
  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: '[email protected]',
  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: '[email protected]', 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_token

See examples/jwt_authentication_example.rb for complete usage patterns.

Currency Conversions (JWT Required)

Convert between cryptocurrencies at market rates:

# Authenticate first
client.authenticate(email: '[email protected]', 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: '[email protected]', password: 'password')

# Get available payment methods
methods = client.fiat_payout_payment_methods(fiat_currency: 'EUR')
# => {"result" => [{"provider" => "transfi", "methods" => [...]}]}

# Create payout account
 = client.(
  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: ['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
end

Documentation

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:

  1. Create account at https://account-sandbox.nowpayments.io/
  2. Generate API key from dashboard
  3. Generate IPN secret for webhooks
  4. Add to .env file

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_secret

Examples

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 4567

Development

# 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 console

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b feature/my-feature)
  3. Run tests (bundle exec rspec)
  4. Commit your changes (git commit -am 'Add feature')
  5. Push to the branch (git push origin feature/my-feature)
  6. Create a Pull Request

Security

Report security vulnerabilities to: [email protected]

Never commit API keys or secrets. Always use environment variables.

License

MIT License - see LICENSE.txt

Support