Developers

API Documentation

Everything you need to accept crypto payments through UnGateway: authentication, payments, balances, conversions, and signed webhooks.

1. Introduction

The UnGateway API lets you accept cryptocurrency payments, read your balances, convert holdings into stablecoins, and receive webhooks for payment events. It is a typed REST API that returns predictable JSON.

The base URL for all requests is:

base url
https://api.ungateway.cc

All monetary amounts are decimal strings, never numbers — precision matters for money, so "50.00" is sent and returned as a string.

2. Authentication

Authenticated endpoints require your secret API key. Generate and manage keys in the developer dashboard. Only a hash of each key is stored — keep the secret safe, and treat it like a password.

Send it on either header:

http
Authorization: Bearer <API_KEY>
# or
X-API-Key: <API_KEY>
EndpointAuth
GET /healthnone
GET /v1/currenciesnone
POST /v1/paymentsrequired
GET /v1/payments/{id}required
GET /v1/balancesrequired
POST /v1/conversionsrequired
GET /v1/conversions/{id}required
POST /v1/payoutsrequired
GET /v1/payouts/{id}required

Errors use standard status codes: 401 (missing/invalid key), 400 (bad request), 404 (not found), 429 (rate limit or too many pending).

3. List currencies

GET/v1/currenciesno auth

Lists the currencies you can transact in (active and backed by a configured chain). No auth required. Use each id as the pay_asset_id when creating a payment.

bash
curl https://api.ungateway.cc/v1/currencies

Response — 200

json
[
  {
    "id": "eth",
    "symbol": "ETH",
    "name": "Ethereum",
    "chain": "ethereum",
    "decimals": 18,
    "min_confirmations": 12,
    "image": "https://storage.ungateway.cc/currencies/eth.svg"
  },
  {
    "id": "usdt-trc20",
    "symbol": "USDT",
    "name": "Tether (TRC-20)",
    "chain": "tron",
    "decimals": 6,
    "min_confirmations": 19,
    "image": "https://storage.ungateway.cc/currencies/usdt-trc20.svg"
  }
]
FieldTypeDescription
idstringUse as pay_asset_id.
symbolstringTicker (e.g. ETH).
namestringHuman name.
chainstringUnderlying chain (ethereum, tron, bitcoin, …).
decimalsintBase-unit decimals for the asset.
min_confirmationsintConfirmations before a deposit counts as paid.
imagestringURL of the currency logo (SVG). Use it to display the coin in your checkout.

4. Create a payment

POST/v1/paymentsauth required

Prices a USD amount into crypto and allocates a single-use deposit address. Redirect the buyer to the returned payment_url for the hosted checkout, or build your own UI from address and pay_amount (and tag if present).

Request body

FieldTypeRequiredDescription
pay_asset_idstringyesAsset the buyer pays in (an id from /v1/currencies).
amount_usdstring (decimal)yesBase amount you want, in USD, before fees. Must be > 0.
merchant_order_idstringnoYour reference. Idempotent: repeating it returns the existing payment.
fallback_urlstringnoAn http(s) URL the buyer returns to after paying.

Auto-exchange, settlement asset, and commission are account settings configured by the operator on your merchant record — they are not request fields.

Example request

bash
curl -X POST https://api.ungateway.cc/v1/payments \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "pay_asset_id": "usdt-trc20",
        "amount_usd": "50.00",
        "merchant_order_id": "order-9001"
      }'

Response — 201 Created

json
{
  "id": "b6f1c2de-1111-2222-3333-444455556666",
  "status": "created",
  "payment_url": "https://www.ungateway.cc/payments/b6f1c2de-1111-2222-3333-444455556666",
  "pay_asset_id": "usdt-trc20",
  "pay_amount": "53.000000",
  "address": "TXk...9aB",
  "tag": null,
  "expires_at": "2026-06-18T12:34:56Z",
  "price": {
    "amount": "50.00",
    "currency": "USD",
    "total": "53.00",
    "rate": "1.00"
  },
  "fees": {
    "base": "50.00",
    "platform_fee": "0.50",
    "merchant_commission": "0",
    "network_fee": "3.00"
  },
  "created_at": "2026-06-18T11:34:56Z"
}
FieldTypeDescription
iduuidPayment id (use with GET /v1/payments/{id} and in webhooks).
statusstringLifecycle status.
payment_urlstringHosted checkout page for this payment — redirect the buyer here instead of building your own payment UI.
pay_amountstringExact amount the buyer must send, in the pay asset.
addressstringSingle-use deposit address to show the buyer.
tagstring | nullMemo/destination tag, if the asset needs one.
expires_atdatetime | nullPay before this or the payment expires.
price.totalstringTotal USD the buyer pays (base + fees).
fees.*stringUSD breakdown: base, platform_fee, merchant_commission, network_fee.

Errors

CodeWhen
400Unknown/inactive pay_asset_id; amount below the minimum; unsupported chain; invalid fallback_url.
401Missing/invalid API key.
429Rate limit exceeded, or too many pending payments.

5. Get a payment

GET/v1/payments/{id}auth required

Fetch a payment's current state. Same body shape as the create response, with status and the received amount reflected. Poll this, or rely on webhooks (recommended). Returns 404 if the id doesn't exist for your merchant.

bash
curl https://api.ungateway.cc/v1/payments/b6f1c2de-1111-2222-3333-444455556666 \
  -H "Authorization: Bearer $API_KEY"

6. Payment statuses

A payment moves through these statuses. Webhooks fire only on the meaningful transitions.

StatusMeaning
createdAddress assigned, awaiting funds.
detectedA deposit was seen (unconfirmed).
confirmingOn-chain, below min_confirmations.
paidConfirmed, full expected amount.
overpaidConfirmed, more than expected.
underpaidConfirmed, less than expected.
exchangingRouted to auto-exchange.
completedNet amount credited to balance.
expiredNo payment before expires_at.
failed / cancelledTerminal failure / cancelled.

7. Get balances

GET/v1/balancesauth required

Your current balances, aggregated by currency symbol. Chain variants of the same currency (e.g. USDT on Tron, Ethereum, Solana) are pooled into one line. Only non-zero balances are returned.

bash
curl https://api.ungateway.cc/v1/balances \
  -H "Authorization: Bearer $API_KEY"

Response — 200

json
{
  "balances": [
    { "asset": "BTC",  "balance": "0.031000000000000000" },
    { "asset": "USDT", "balance": "1250.500000000000000000" }
  ]
}

8. Create a conversion

POST/v1/conversionsauth required

Convert a balance you already hold into a stablecoin (USDT or USDC), charged a 0.5% platform fee. The request is queued: it reserves the balance and returns status: "pending". Poll GET /v1/conversions/{id} for the outcome.

v1 supports converting into USDT/USDC only (selling a volatile asset into a stablecoin).

Request body

FieldTypeRequiredDescription
fromstringyesCurrency symbol to convert from (e.g. BTC). Case-insensitive.
tostringyesStablecoin symbol to convert to: USDT or USDC.
amountstring (decimal)yesAmount of from to convert. Must be > 0.

Assets are named by symbol, not chain-specific id. Constraints: minimum value amount × rate ≥ $5; max 10 pending conversions per merchant; from and to must differ.

Example request

bash
curl -X POST https://api.ungateway.cc/v1/conversions \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "from": "BTC", "to": "USDT", "amount": "0.05" }'

Response — 201 Created

json
{
  "id": "c1d2e3f4-aaaa-bbbb-cccc-000011112222",
  "status": "pending",
  "from": "BTC",
  "amount": "0.05",
  "to": "USDT",
  "to_amount": null,
  "platform_fee": null,
  "rate": null,
  "created_at": "2026-07-13T10:00:00Z",
  "completed_at": null
}

to_amount, platform_fee, and rate are null until the conversion fills. Your net credit is to_amount − platform_fee.

9. Get a conversion

GET/v1/conversions/{id}auth required

Fetch a conversion's current state. Same shape as the create response, with the filled amounts reflected once complete. Returns 404if the id doesn't exist for your merchant.

bash
curl https://api.ungateway.cc/v1/conversions/c1d2e3f4-aaaa-bbbb-cccc-000011112222 \
  -H "Authorization: Bearer $API_KEY"
StatusMeaning
pendingReserved and queued; awaiting execution on the exchange.
filledSold and credited; net amount added to your balance.
failedTerminal failure; reserved amount returned unless it may have executed.

10. Create a payout

POST/v1/payoutsauth required

Withdraw a held balance to your verified payout address for an asset. The destination is not supplied in the request. It's the address you have on file for the asset, and it must be verified. The request reserves the balance (debiting your ledger immediately) and queues the withdrawal.

Payout addresses are chain-specific, so payouts are named by the concrete asset_id (e.g. usdt-trc20), not a symbol. There is one payout address per merchant and asset.

Request body

FieldTypeRequiredDescription
asset_idstringyesConcrete asset id to pay out (e.g. usdt-trc20, usdc-erc20).
amountstring (decimal)yesGross amount to withdraw, in the asset, before the payout fee. Must be > 0.

Example request

bash
curl -X POST https://api.ungateway.cc/v1/payouts \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "asset_id": "usdt-trc20", "amount": "100.00" }'

Response — 201 Created

json
{
  "id": "9d0e1f2a-3333-4444-5555-666677778888",
  "status": "requested",
  "asset_id": "usdt-trc20",
  "amount": "100.00",
  "fee": "2.25",
  "net": "97.75",
  "destination_address": "TXk...9aB",
  "destination_tag": null,
  "txid": null,
  "created_at": "2026-07-27T10:00:00Z",
  "completed_at": null
}
FieldTypeDescription
iduuidPayout id (use with GET /v1/payouts/{id}).
statusstringrequested, processing, sent, confirmed, or failed.
amountstringGross amount debited from your balance.
feestringPayout fee deducted from the gross.
netstringAmount that will arrive at the destination (amount - fee).
destination_addressstringYour verified address for this asset.
destination_tagstring | nullMemo/destination tag, if the chain uses one.
txidstring | nullOn-chain tx hash, once the withdrawal is confirmed.

Errors

CodeWhen
400amount ≤ 0; no payout address on file; address not verified; asset not payable; amount below the fee; net below the exchange minimum; payouts temporarily unavailable.
401Missing/invalid API key.
429Rate limit exceeded.

The balance debit is atomic with the payout: if your balance can't cover the gross, the payout isn't created.

11. Get a payout

GET/v1/payouts/{id}auth required

Fetch a payout's current state. Same body shape as the create response, with status, txid, and completed_at reflected as it progresses. Returns 404if the id doesn't exist for your merchant.

bash
curl https://api.ungateway.cc/v1/payouts/9d0e1f2a-3333-4444-5555-666677778888 \
  -H "Authorization: Bearer $API_KEY"

Payout statuses

StatusMeaning
requestedCreated; balance reserved, queued for execution.
processingBeing submitted to the exchange.
sentWithdrawal submitted; awaiting on-chain confirmation.
confirmedSent and confirmed on-chain; txid populated.
failedTerminal failure; the reserved balance is re-credited to your ledger.

12. Webhooks

Webhooks push payment status changes to your server so you don't have to poll. Register and manage endpoints in the webhooks dashboard, where a signing secret is generated for you.

Event types

Events are payment.<status>. Only meaningful transitions are sent (intermediate detected/confirming are not):

  • payment.paid, payment.overpaid, payment.underpaid
  • payment.completed, payment.expired, payment.failed

Delivery

POST to your URL with a JSON body. 10s timeout; any 2xx = success. On failure, redelivered with backoff (≈30s, 2m, 10m, 1h, 6h), up to 6 attempts. Delivery is at-least-once — make handling idempotent (key on payment_id + status).

Headers

HeaderDescription
X-Ungateway-EventThe event type, e.g. payment.paid.
X-Ungateway-Signaturesha256=<hex> — HMAC-SHA256 of the raw request body, keyed by your secret.
X-Ungateway-TimestampRFC3339 time the delivery was attempted.

Payload

json
{
  "event": "payment.paid",
  "payment_id": "b6f1c2de-1111-2222-3333-444455556666",
  "merchant_order_id": "order-1234",
  "status": "paid",
  "pay_asset_id": "eth",
  "pay_amount": "0.034812",
  "received_amount": "0.034812",
  "credited_amount": "102.00",
  "credited_asset_id": "usdt-trc20",
  "address": "0x9858EfFD232B4033E47d90003D41EC34EcaEda94",
  "txids": ["0xabc...123"],
  "timestamp": "2026-06-18T11:40:02Z"
}

credited_amount and credited_asset_id are null until the payment is credited (populated on payment.completed).

Verifying the signature

Compute HMAC-SHA256(secret, raw_body) and compare (constant-time) to the hex in X-Ungateway-Signature after stripping the sha256= prefix. Use the exact raw bytes received — do not re-serialize the JSON first.

node.js
const crypto = require("crypto");

function verify(rawBody, signatureHeader, secret) {
  const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  const got = (signatureHeader || "").replace(/^sha256=/, "");
  return got.length === expected.length &&
         crypto.timingSafeEqual(Buffer.from(got), Buffer.from(expected));
}

// Express: capture the raw body, e.g. app.use(express.raw({ type: "application/json" }))
app.post("/webhooks/ungateway", (req, res) => {
  if (!verify(req.body, req.get("X-Ungateway-Signature"), process.env.WHSEC)) {
    return res.status(401).end();
  }
  const event = JSON.parse(req.body.toString());
  // ... handle idempotently by event.payment_id + event.status ...
  res.status(200).end();
});
python
import hmac, hashlib

def verify(raw_body: bytes, signature_header: str, secret: str) -> bool:
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    got = (signature_header or "").removeprefix("sha256=")
    return hmac.compare_digest(got, expected)

13. Health check

GET/healthno auth

Liveness probe. No auth. Returns 200 with body ok.

bash
curl https://api.ungateway.cc/health