Merchant API

Integrate crypto deposit wallets

Create deposit addresses, verify split configuration, and receive movement webhooks. Use ephemeral wallets for one-shot payments or client mode for reusable customer addresses.

Overview

Base URL
https://nxtgatew.com

JSON API with UTF-8 encoding. CORS allows browser requests with the proper headers. Paths below are relative to the base URL.

Quick start

1

Get API credentials

At signup you receive an X-API-Key and X-API-Secret pair (merchant / PSP / admin).

2

Create a deposit wallet

Call POST /api/v1/wallets/create with your payout address, callback, and network.

3

Give the deposit address

Return data.address to the payer. Store ipn_token to verify callbacks.

4

Verify on EVM

Call GET /api/v1/wallets/check-config to confirm database vs on-chain split config.

curl -X POST "https://nxtgatew.com/api/v1/wallets/create" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: YOUR_KEY" \
  -H "X-API-Secret: YOUR_SECRET" \
  -d '{
    "address": "0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
    "callback": "https://your-site.com/webhooks/nxtgatew",
    "network": "polygon",
    "mode": "ephemeral"
  }'

Authentication

Every merchant endpoint requires both headers:

X-API-Key: YOUR_KEY
X-API-Secret: YOUR_SECRET
Accepted roles

merchant, psp, or admin with status = active.

Failure

Missing or invalid credentials → HTTP 401.

Never expose the API secret in a public frontend. Prefer server-side calls or the interactive console with sessionStorage only.

Endpoints

All endpoints require X-API-Key + X-API-Secret.

Create deposit wallet

POST /api/v1/wallets/create Auth

Creates or reuses a receive address controlled by the platform, tied to your account, with a callback URL and optional affiliate / sub-affiliate wallets. Client mode uses the same optional split fields (affiliate_address, affiliate_percent, subaff_address, subaff_percent) — send them on each POST /wallets/create, including when reusing a client_id.

Modes

modeBehavior
ephemeralDefault. New deposit address on every call.
clientGet-or-create by (account, network, client_id). Requires migration 015. Only one open inbound payment at a time (else 409). Response includes private_key for export / migration.

Headers

ParameterRequiredTypeDescription
X-API-KeyYesstringYour API key.
X-API-SecretYesstringMatching API secret.
Content-TypeYesapplication/jsonRequest body must be JSON.

Body

FieldRequiredTypeDescription
addressYesstringMerchant payout address (final destination after split).
callbackYesURLIPN / webhook URL. Alias: callback_url.
networkNoethereum · polygon · tron · solanaDefault: ethereum.
modeNoephemeral · clientDefault: ephemeral.
client_idIf clientstringRequired when mode=client (1–128 chars).
affiliate_address / affiliate_percentNostring / numberAffiliate share as % of gross (e.g. 1 = 1%). Applies to ephemeral and client; on client reuse, updates DB when sent (otherwise keeps stored address / %).
subaff_address / subaff_percentNostring / numberSub-affiliate share as % of gross. Same rules as affiliate fields.
ipn_tokenNostringYour correlation token; otherwise platform generates 64 hex chars.
transaction_idNostringYour payment / order id for this deposit (e.g. 183). Echoed in all wallet webhooks for this cycle. Alias: meta.transaction_id. Requires migration 024.
metaNoobjectOptional metadata; meta.transaction_id is accepted like the root field.

Success

201 new wallet · 200 client wallet reused (reused: true). Response includes address, ipn_token, wallet_mode, optional client_id, optional transaction_id (when sent on create), platform_percent, affiliate fields. In client mode only, private_key is also returned (plaintext deposit key for export / migration) — never returned for ephemeral wallets.

Verify split configuration

GET /api/v1/wallets/check-config Auth

Returns database fields plus on-chain configuration (Ethereum / Polygon when the Node verification script is available).

Query parameters

ParameterRequiredTypeDescription
wallet_addressYesstringDeposit address from create (data.address).
networkNostringDefault: polygon. Must match the wallet.

Response includes database, on_chain, and match (true when on-chain merchant matches DB).

Webhooks

The callback URL receives POST JSON. If ipn_token was set, verify header X-IPN-Token.

When you send transaction_id on create, the same value is included on every webhook for that deposit cycle — use it to match IPNs to your internal order (especially client reusable wallets where wallet_id and client_id repeat).

EventWhen
wallet.balance_changedAfter each new indexed movement and ledger write.
wallet.movements_snapshotOnce per sync cycle when movements are reconciled and split is complete.
wallet.split_completeAfter on-chain split succeeds (legacy split callback JSON).

Common fields: wallet_id, deposit_address, ipn_token, wallet_mode, client_id (client mode), transaction_id (when provided on create).

Failed deliveries are retried via cron/dispatch-wallet-webhooks-web.php?token=… (same CRON_SECRET_TOKEN as other URL crons).

Content-Type: application/json
X-IPN-Token: <ipn_token>

Common errors

HTTPMeaning
400Invalid JSON, missing fields, unsupported network/mode, bad address/callback
401Invalid or missing API credentials
403Merchant account inactive
404Wallet not found or not yours
409Duplicate address, or client wallet still processing a previous deposit
503transaction_id sent but migration 024 not applied
500Wallet generation / DB / migration missing
{
  "success": false,
  "error": "Human-readable message explaining the issue"
}

Wallet lifecycle

created funded split

Explorer sync indexes deposits; split cron distributes to merchant / platform / affiliates. In client mode, reuse the same address only after the previous cycle reaches split. Send a new transaction_id on each POST /wallets/create so webhooks map to the correct merchant order.

NFT / SBT module

Self-serve NFT layer under /api/v1/nft/* for any registered merchant. Supported asset types: membership (soulbound), digital_license, order_receipt (one NFT per order), plus reward / certificate / collectible. It does not change POST /api/v1/wallets/create.

Full reference

Sections below · OpenAPI tag NFT · API console

Console

Collection NFT: Mode A mint, Mode B import, lifecycle, webhooks.

Mint confirmation + webhooks: cron process-nft-operations-web.php (platform-managed in production).

Mint modes A & B

Two complementary paths. Pick one per payment — never both for the same purchase (that would double-mint).

Mode A — Platform mintMode B — External mint + import
When You confirm payment off-chain (card, on-ramp, invoice…), then ask NxtGateW to mint. A third party already minted on-chain (payable SC call with your collection contract).
API POST /api/v1/nft/nfts202 POST /api/v1/nft/nfts/import with tx_hash → indexes Transfer mint
On-chain actor NxtGateW operator wallet External minter (must be authorized on your contract if the SC uses a whitelist)
Truth signal Webhook nft.minted after cron confirm Webhook nft.minted after successful import

Mode A — typical flow

  1. Optional: POST /wallets/create if you also settle native crypto to a deposit + split.
  2. POST /nft/wallets → recipient wallet_id.
  3. After payment confirmed → POST /nft/nfts with Idempotency-Key = your order id.
  4. Handle nft.minted (or poll GET /operations/{id}).

Mode B — typical flow

  1. Collection contract_address set; external minter allowed on-chain if required.
  2. External tx mints NFT to the user wallet (same contract as the collection).
  3. POST /nft/nfts/import with tx_hash, collection_id, asset_type, rich attributes (name, order_ref, …).
  4. Do not call POST /nft/nfts for that tx.
Metadata: set a public HTTPS tokenURI (or attributes.name / description on import/mint). Explorers often ignore data: URIs. Suggested names: MembershipBronze_{user_id}, Order_{order_ref}.
Membership: one active pass per wallet. Tier upgrades = revoke/retire previous + mint a new level (Mode A), or import a new on-chain mint (Mode B) after clearing the active pass.

NFT quick start (Mode A)

1

Link wallet

POST /api/v1/nft/wallets with external_user_id (+ optional callback).

2

Create collection

POST /api/v1/nft/collections (membership / digital_license / order_receipt), then PATCH contract_address.

3

Mint (Mode A)

POST /api/v1/nft/nfts with Idempotency-Key202. Cron confirms mint.

4

Lifecycle

Activate / suspend / migrate / burn via POST /api/v1/nft/nfts/{id}/…. On-chain when token_id is set.

curl -X POST "$BASE/api/v1/nft/nfts" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: KEY" -H "X-API-Secret: SECRET" \
  -H "Idempotency-Key: order_89452_membership" \
  -d '{
    "asset_type": "membership",
    "collection_id": "col_xxx",
    "wallet_id": "wal_xxx",
    "external_user_id": "usr_84521",
    "external_reference": "order_89452",
    "transfer_policy": "soulbound",
    "attributes": {
      "name": "MembershipBronze_usr_84521",
      "membership_level": "bronze",
      "user_id": "usr_84521"
    }
  }'

Mode B import example:

curl -X POST "$BASE/api/v1/nft/nfts/import" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: KEY" -H "X-API-Secret: SECRET" \
  -H "Idempotency-Key: import_0xabc…" \
  -d '{
    "tx_hash": "0x…",
    "collection_id": "col_xxx",
    "network": "polygon",
    "asset_type": "order_receipt",
    "external_user_id": "usr_84521",
    "external_reference": "order_89452",
    "attributes": {
      "name": "Order_order_89452",
      "order_ref": "order_89452",
      "user_id": "usr_84521",
      "amount_pol": "10.5",
      "payment_rail": "external_checkout"
    }
  }'

NFT auth & HMAC

Same X-API-Key + X-API-Secret as deposit wallets. Write ops that create side effects also require Idempotency-Key.

Optional NxtGateW HMAC — if you send X-Api-Signature (or X-Signature), also send X-Timestamp (±5 min) and unique X-Nonce. Replay of a nonce → 401.

METHOD + "\n" + PATH + "\n" + TIMESTAMP + "\n" + NONCE + "\n" + SHA256(raw_body)
# HMAC-SHA256 hex with api_secret
# PATH example: /api/v1/nft/nfts
# macOS LibreSSL: use awk '{print $NF}' on openssl dgst -hex output

NFT endpoints (summary)

AreaPaths
WalletsPOST/GET /nft/wallets, .../by-external-user/{id}, .../assets
CollectionsCRUD-ish + /deploy, /pause, /unpause
NFTsMode A mint POST /nfts, Mode B POST /nfts/import, get, patch, lifecycle, history, by-token
OpsGET /nft/operations/{operation_id}
WebhooksPOST/GET /nft/webhooks, POST .../test

Paused collection → mint rejected. One active membership NFT per wallet (NFT_ALREADY_EXISTS).

NFT webhooks

Register with POST /api/v1/nft/webhooks (url, optional secret, subscribed_events). Manage with PATCH /webhooks/{id} (rotate_secret, status) or DELETE (soft-disable). Deliveries are signed:

X-NxtGateW-Event-Id: evt_…
X-NxtGateW-Timestamp: <unix>
X-NxtGateW-Signature: sha256=<HMAC(timestamp + "." + body, endpoint_secret)>
EventWhen
nft.mint_pendingMint queued
nft.mintedMode A cron confirm or Mode B import success
nft.updated / nft.burnedLifecycle changes
nft.testManual test endpoint

Local smoke catcher: /tools/webhook-catcher.php (writes logs/webhook-catcher.log). Prefer your own URL over httpbin (often times out).