# Migrate from NOWPayments

> Move from NOWPayments to CoinCircuit: endpoint mapping, webhook re-signing, and a step-by-step checklist.

Section: Migration
Source: https://coincircuit.io/docs/guides/migrate-from-nowpayments/
Interactive version: https://coincircuit.io/api-reference?tab=guides&guide=migrate-from-nowpayments

CoinCircuit and NOWPayments share the same core model — you create a payment, your customer pays a crypto address, and you receive a signed webhook when it settles. The concepts map directly, so migration is mostly a find-and-replace of endpoints, field names, and the signing algorithm.

## Concept mapping

| NOWPayments | CoinCircuit | Notes |
| :--- | :--- | :--- |
| Payment | Payment (checkout session) | Same idea: one charge, one address, one webhook. |
| Invoice | Invoice | Itemized, multi-line billing with a pay link. |
| IPN callback | Webhook | Same delivery model; different signing algorithm. |
| IPN secret key | Webhook secret | Used to verify the signature on each event. |
| `x-nowpayments-sig` header | `x-coincircuit-signature` header | Header name changes; see signing below. |
| Primary balance | Balance | Per-asset balance credited after each payment. |
| Mass payouts | Payouts | Send crypto to any wallet in a single API call. |
| Custodial recurring payments | — | Use invoices + webhooks to rebuild recurring billing. |

---

## Step 1 — Create payments

#### NOWPayments flow (before)

NOWPayments required two pre-flight calls — check the minimum amount, get an estimated price — before creating a payment.

```http
GET /v1/min-amount?currency_from=btc&currency_to=eth
GET /v1/estimate?amount=100&currency_from=usd&currency_to=btc
POST /v1/payment
```

```json
{
  "price_amount": 100,
  "price_currency": "usd",
  "pay_currency": "btc",
  "ipn_callback_url": "https://your-site.com/webhook",
  "order_id": "order_123",
  "order_description": "T-shirt"
}
```

#### CoinCircuit flow (after)

One call. CoinCircuit prices the payment and returns the crypto amount to collect in the payment response. You set the `asset` and `chain`, or omit them to let the customer pick at checkout.

```http
POST https://api.coincircuit.io/api/v1/payments
x-api-key: sk_live_your_key
Content-Type: application/json
```

```json
{
  "title": "T-shirt",
  "description": "Order #order_123",
  "amount": "100.00",
  "currency": "USD",
  "customer": { "email": "customer@example.com" },
  "webhookUrl": "https://your-site.com/webhook",
  "metadata": { "orderId": "order_123" }
}
```

The response includes `checkoutUrl` — redirect the customer there. CoinCircuit locks the crypto rate for the asset and chain, whether you set them or the customer picks at checkout.

### Field mapping

| NOWPayments field | CoinCircuit field | Notes |
| :--- | :--- | :--- |
| `price_amount` | `amount` | Pass as a string, e.g. `"100.00"`. |
| `price_currency` | `currency` | `NGN` or `USD`. |
| `pay_currency` | `asset` | Optional. Omit to let the customer choose. |
| `ipn_callback_url` | `webhookUrl` | Per-payment webhook URL. |
| `order_id` / `order_description` | `metadata` | Store any key-value data you need in webhooks. |
| `success_url` | `successUrl` | Redirect after successful payment. |
| `cancel_url` | `cancelUrl` | Redirect after cancellation. |

---

## Step 2 — Replace invoices

#### NOWPayments (before)

```http
POST /v1/invoice
```

```json
{
  "price_amount": 500,
  "price_currency": "usd",
  "order_id": "inv_001",
  "order_description": "Consulting — June",
  "success_url": "https://your-site.com/thank-you",
  "ipn_callback_url": "https://your-site.com/webhook"
}
```

#### CoinCircuit (after)

```http
POST https://api.coincircuit.io/api/v1/invoices
x-api-key: sk_live_your_key
Content-Type: application/json
```

```json
{
  "description": "Consulting — June",
  "currency": "USD",
  "expiresAt": "2026-08-01T00:00:00Z",
  "customer": { "email": "client@example.com", "firstName": "Ada" },
  "reference": "inv_001",
  "successUrl": "https://your-site.com/thank-you",
  "items": [
    { "name": "Consulting", "quantity": 10, "unitPrice": 50 }
  ]
}
```

CoinCircuit invoices are line-item based. If NOWPayments was your invoicing layer, map each product or service into an `items` entry. The `reference` field replaces `order_id`.

---

## Step 3 — Migrate webhooks

Both platforms POST a JSON body to your callback URL when a payment status changes. The main difference is the **signature algorithm**.

#### NOWPayments signature (before)

1. Sort the request body by keys recursively.
2. Serialize with `JSON.stringify(sortedBody)`.
3. HMAC-SHA512 the string with your IPN secret.
4. Compare against the `x-nowpayments-sig` header.

```js
const hmac = crypto.createHmac('sha512', ipnSecret);
hmac.update(JSON.stringify(sortObject(body)));
const expected = hmac.digest('hex');
const isValid = expected === req.headers['x-nowpayments-sig'];
```

#### CoinCircuit signature (after)

CoinCircuit signs the timestamped payload with HMAC-SHA256 and sends the result in the `x-coincircuit-signature` header as `v1=<hex>`, alongside an `x-coincircuit-timestamp` header. Rebuild the signed string as `timestamp.payload`, recompute the HMAC, strip the `v1=` prefix, and compare in constant time.

```js
const crypto = require('crypto');

function verifyWebhookSignature(payload, signature, secret, timestamp) {
  try {
    const signedPayload = timestamp ? `${timestamp}.${payload}` : payload;
    const expectedSignature = crypto.createHmac('sha256', secret).update(signedPayload, 'utf8').digest('hex');
    // The signature header is "v1=<hex>"; strip the version prefix before comparing.
    const received = signature.replace(/^v1=/, '');
    return crypto.timingSafeEqual(Buffer.from(received, 'hex'), Buffer.from(expectedSignature, 'hex'));
  } catch (_) {
    return false;
  }
}

// Express example
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const payload = req.body.toString('utf8');
  const signature = req.headers['x-coincircuit-signature'];
  const timestamp = req.headers['x-coincircuit-timestamp'];
  if (!verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET, timestamp)) {
    return res.status(401).send('Invalid signature');
  }
  const event = JSON.parse(payload);
  // handle event
  res.sendStatus(200);
});
```

> Use `express.raw()` (or equivalent) so you verify the exact bytes CoinCircuit signed, before any JSON parsing.

### Webhook event mapping

| NOWPayments status | CoinCircuit event | Trigger |
| :--- | :--- | :--- |
| `waiting` | — | CoinCircuit omits pre-deposit waiting events. |
| `confirming` | `transaction.received` | Deposit detected on-chain, awaiting confirmation. |
| `confirmed` | `transaction.confirmed` | Transaction confirmed on the blockchain. |
| `finished` | `payment.completed` | Payment settled in full — fulfil the order on this. |
| `partially_paid` | `payment.partial` | Partial deposit received. |
| `failed` / `expired` | `payment.expired` | Session expired or payment could not be completed. |
| `refunded` | `refund.success` | Refund confirmed on-chain. |

Fulfil orders on `payment.completed` (was `finished` in NOWPayments). Store your internal order ID in `metadata` when creating the payment — it comes back on every event.

---

## Step 4 — Migrate payouts

#### NOWPayments mass payout (before)

NOWPayments batched payouts in a single `/payout` call with an array of withdrawals and a separate IPN secret.

#### CoinCircuit payout (after)

Save the recipient once, then initiate per payout. Check fees before sending.

```http
POST https://api.coincircuit.io/api/v1/recipients
x-api-key: sk_live_your_key
Content-Type: application/json

{
  "type": "crypto_address",
  "label": "Vendor A",
  "details": { "chain": "base", "address": "0xRecipientAddress" }
}
```

```http
GET https://api.coincircuit.io/api/v1/payouts/fees
x-api-key: sk_live_your_key
```

```http
POST https://api.coincircuit.io/api/v1/payouts
x-api-key: sk_live_your_key
Content-Type: application/json

{
  "method": "crypto",
  "currency": "USDC",
  "amount": "500.00",
  "recipientId": "recipient-id-from-step-above"
}
```

Listen for `payout.success` or `payout.failed` webhooks to confirm the result.

---

## Step 5 — Check payment status

#### NOWPayments (before)

```http
GET /v1/payment/{paymentId}
```

#### CoinCircuit (after)

```http
GET https://api.coincircuit.io/api/v1/payments/reference/{reference}
x-api-key: sk_live_your_key
```

Retrieve by the `reference` returned when you created the payment. To list all payments:

```http
GET https://api.coincircuit.io/api/v1/payments?page=1&size=20
```

---

## Step 6 — Test in the sandbox

CoinCircuit has a full sandbox environment on testnet. Point your integration at the sandbox base URL with test API keys before going live.

```diff
- https://api.coincircuit.io/api/v1
+ https://sandbox-api.coincircuit.io/api/v1
```

Use the **Simulate payment** button on the sandbox checkout page to trigger payment outcomes (full, partial, overpayment, AML failure) without moving real funds. See the [Sandbox Environment](https://coincircuit.io/guides/sandbox) guide for faucet links and testnet details.
