1. Home
  2. Developers
Developers

Start accepting crypto in minutes

A REST API compatible with NOWPayments v1: the same endpoints, fields, status values and currency codes. Authenticate with x-api-key and receive status updates as signed IPNs.

API base URLhttps://omniwallet.ewin888.com/api/v1/
Sandboxhttps://omniwallet-dev.ewin888.com/api/v1/

Quickstart

  1. 01

    Get an API key

    Sign up, open the console and create a key under Developers → API keys, then set an IPN secret under IPN. We recommend building against the sandbox first.

  2. 02

    Create a payment

    Call POST /v1/payment with the amount, price currency and pay currency. The response contains pay_address, the payment’s unique address, and pay_amount, the amount due.

    POST /v1/paymentcurl
    curl -X POST "https://omniwallet.ewin888.com/api/v1/payment" \
      -H "x-api-key: YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: order-A-1024" \
      -d '{
        "price_amount": 120,
        "price_currency": "usd",
        "pay_currency": "usdttrc20",
        "order_id": "A-1024",
        "order_description": "Pro plan, 12 months",
        "ipn_callback_url": "https://example.com/ipn"
      }'
    ResponseJSON
    {
      "payment_id": 355446374401130,
      "payment_status": "waiting",
      "pay_address": "TJ8nq3M5yVfQ9C2dWkPz7aH4sLrE6uXbGt",
      "price_amount": 120,
      "price_currency": "usd",
      "pay_amount": 120.02,
      "pay_currency": "usdttrc20",
      "order_id": "A-1024",
      "network": "TRC20",
      "expiration_estimate_date": "2026-10-02T09:15:00.000Z",
      "omni_payment_url": "https://omniwallet.ewin888.com/payment/4fKq9ZtR2mXcL8vBnW1pYs"
    }
  3. 03

    Collect the payment

    Show the address and amount (or a QR code) to your customer — or use POST /v1/invoice to create a hosted invoice and redirect the customer to its invoice_url.

    POST /v1/invoicecurl
    curl -X POST "https://omniwallet.ewin888.com/api/v1/invoice" \
      -H "x-api-key: YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "price_amount": 49.9,
        "price_currency": "usd",
        "order_id": "A-1025",
        "order_description": "Starter plan",
        "ipn_callback_url": "https://example.com/ipn",
        "success_url": "https://example.com/thanks",
        "cancel_url": "https://example.com/cart"
      }'
    ResponseJSON
    {
      "id": "355447603200201",
      "order_id": "A-1025",
      "price_amount": "49.9",
      "price_currency": "usd",
      "pay_currency": null,
      "invoice_url": "https://omniwallet.ewin888.com/invoice/8hTz2QpLm4XwN7cVb1RkYe",
      "success_url": "https://example.com/thanks",
      "cancel_url": "https://example.com/cart",
      "created_at": "2026-10-01T09:20:00.000Z"
    }
  4. 04

    Receive IPNs

    Every time the payment status changes we POST to your ipn_callback_url. Verify the signature first, then update the order based on payment_status.

    Verify IPNs
  5. 05

    Go live

    Once your business is verified, create a new API key and IPN secret in production and switch the base URL.

Verify IPN signatures

We compute an HMAC-SHA512 with your IPN secret over the JSON body with keys sorted alphabetically, and send it hex-encoded in the x-omni-sig header. The request body we send is exactly that sorted JSON, so you can verify the raw body as received.

  • Compare signatures in constant time
  • IPNs can arrive more than once or out of order: de-duplicate by payment_id and status, and call GET /v1/payment/{id} for the latest state when in doubt
  • Any 2xx response acknowledges the IPN; other responses and timeouts are retried automatically
ExpressNode.js
import crypto from 'node:crypto';
import express from 'express';

const app = express();
const IPN_SECRET = process.env.OMNI_IPN_SECRET;

// Keep the raw body: the signature covers the exact bytes we send.
app.post('/ipn', express.raw({ type: 'application/json' }), (req, res) => {
  const received = String(req.get('x-omni-sig') || '');
  const expected = crypto
    .createHmac('sha512', IPN_SECRET)
    .update(req.body)
    .digest('hex');

  const valid = received.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
  if (!valid) return res.status(401).send('invalid signature');

  const payment = JSON.parse(req.body.toString('utf8'));
  // Idempotent update: the same status can be delivered more than once.
  // await orders.applyStatus(payment.order_id, payment.payment_status);
  res.sendStatus(200);
});

app.listen(3000);
PHPPHP
<?php
$secret   = getenv('OMNI_IPN_SECRET');
$raw      = file_get_contents('php://input');
$received = $_SERVER['HTTP_X_OMNI_SIG'] ?? '';

// The signature covers the exact bytes we send.
$expected = hash_hmac('sha512', $raw, $secret);
if (!hash_equals($expected, $received)) {
    http_response_code(401);
    exit('invalid signature');
}

$payment = json_decode($raw, true);
// Idempotent update: the same status can be delivered more than once.
// apply_status($payment['order_id'], $payment['payment_status']);
http_response_code(200);
FlaskPython
import hashlib
import hmac
import json
import os

from flask import Flask, abort, request

app = Flask(__name__)
IPN_SECRET = os.environ["OMNI_IPN_SECRET"].encode()

@app.post("/ipn")
def ipn():
    raw = request.get_data()  # the exact bytes we signed
    expected = hmac.new(IPN_SECRET, raw, hashlib.sha512).hexdigest()
    received = request.headers.get("x-omni-sig", "")
    if not hmac.compare_digest(expected, received):
        abort(401)

    payment = json.loads(raw)
    # Idempotent update: the same status can be delivered more than once.
    # apply_status(payment["order_id"], payment["payment_status"])
    return "", 200
Example IPN bodyJSON
{
  "actually_paid": 120.02,
  "actually_paid_at_fiat": 120,
  "invoice_id": null,
  "order_description": "Pro plan, 12 months",
  "order_id": "A-1024",
  "outcome_amount": 119.42,
  "outcome_currency": "usdttrc20",
  "parent_payment_id": null,
  "pay_address": "TJ8nq3M5yVfQ9C2dWkPz7aH4sLrE6uXbGt",
  "pay_amount": 120.02,
  "pay_currency": "usdttrc20",
  "payment_id": 355446374401130,
  "payment_status": "finished",
  "price_amount": 120,
  "price_currency": "usd",
  "purchase_id": null,
  "updated_at": "2026-10-01T09:17:42.000Z"
}

Payment statuses

Status values are identical to NOWPayments.

StatusMeaning
waiting
Waiting
Waiting for the customer to pay
confirming
Confirming
Transaction detected, waiting for block confirmations
confirmed
Confirmed
Confirmed and credited
sending
Settling
Being settled to your wallet
partially_paid
Partially paid
Underpaid; the customer can top up before expiry
finished
Finished
Payment complete
failed
Failed
Payment failed, e.g. a sanctions list match
refunded
Refunded
Refunded to the payer
expired
Expired
Not paid in time

Migrate from NOWPayments

The API is compatible, so most of your code stays the same.

  1. Change the base URLReplace https://api.nowpayments.io/v1/ with the OmniWallet URL below.
  2. Use an OmniWallet API keyThe header is still x-api-key.
  3. Update IPN verificationUse your OmniWallet IPN secret and the x-omni-sig header. To keep your existing handler unchanged, enable the x-nowpayments-sig compatibility header in the console.
  4. Check supported coinsCurrency codes are the same (for example usdttrc20 or usdcsol), but only the coins we support are available. Check GET /v1/merchant/coins or the supported coins page.
  5. Review payout settingsPayouts still use a 5-minute JWT from POST /v1/auth. In addition, API keys with payout permissions need an IP allowlist, and destination addresses must be whitelisted first.
API base URLdiff
- https://api.nowpayments.io/v1/
+ https://omniwallet.ewin888.com/api/v1/

Stays the same

  • Endpoint paths and HTTP methods
  • Request and response field names
  • Payment and payout status values
  • Currency codes
  • IPN signature algorithm

Differences to note

  • The IPN signature header is x-omni-sig (optionally also sent as x-nowpayments-sig)
  • OmniWallet extension fields start with omni_
  • Fiat payouts are not supported (HTTP 501)
  • Settlements always go to the wallet registered in the console
  • The sandbox runs on real blockchain testnets

Sandbox

The sandbox is a separate deployment connected to each chain’s testnet, with exactly the same API as production.

  • Accounts, API keys and data are separate from production, so sign up separately; merchant verification is approved automatically
  • Pay with testnet coins; transactions are visible in each testnet’s block explorer
  • Test coins have no value — never send mainnet funds to sandbox addresses
NetworkTestnetAvailable currency codes
EthereumEthereum Sepoliaeth usdc
BNB Smart ChainBNB Smart Chain Testnetbnbbsc usdtbsc
PolygonPolygon Amoymaticmainnet usdcmatic
Avalanche C-ChainAvalanche Fujiavax usdcavax
ArbitrumArbitrum Sepoliaetharb usdcarb
BaseBase Sepoliaethbase usdcbase
OptimismOP Sepoliaethop usdcop
TRONTRON Niletrx usdttrc20
SolanaSolana Devnetsol usdcsol
BitcoinBitcoin Testnet4btc

Idempotency and retries

Every POST request that creates a resource accepts an Idempotency-Key header. If a request times out, retry it with the same key and no duplicate payment or payout will be created.

  • Same key, same body: the original result is returned
  • Same key, different body: HTTP 422 IDEMPOTENCY_KEY_REUSED
  • First request still in progress: HTTP 409 IDEMPOTENCY_IN_FLIGHT — retry shortly
HTTPHTTP
POST /api/v1/payout HTTP/1.1
Host: omniwallet.ewin888.com
x-api-key: YOUR_API_KEY
Authorization: Bearer YOUR_JWT
Idempotency-Key: 6c0a8e6e-3f5b-4f7a-9a52-1f0c2d9b7e41
Content-Type: application/json

Start accepting crypto payments

Sign up for free, build your integration in the sandbox, and switch to production once your business is verified.