Developer Documentation

Integrate sha3 into your application to accept cryptocurrency payments. Merchant access is onboarding-controlled: complete account setup and any required review before using issued API credentials for server-to-server calls.

Quick Start

  1. Create a merchant account and submit your business details to begin onboarding
  2. Complete any required review before relying on issued API Keys or IPN Secrets in production
  3. Use the API Key in the X-API-Key header for approved payment endpoints
  4. Create orders, get deposit addresses, and verify webhook signatures using your IPN Secret

Your key is only used in-browser and never stored.

Quick Select
https://api.sha3.net

All payment API calls require an API Key sent in the X-API-Key header. API keys are activation-gated and should only be treated as usable after the merchant account has completed the required onboarding and review steps.

API Key (Server-to-Server)

Include your API key in every request to /v1/pay/ endpoints.

http
GET /v1/pay/deposit-address?user_id=user123&currency=USDT_TRC20
Host: api.sha3.net
X-API-Key: your-api-key-here

⚠️ Never expose your API Key in client-side code. All payment API calls should be made from your server.

Onboarding is controlled. Creating an account does not automatically mean the merchant is approved for unrestricted production use, payouts, or live credential issuance.

API Key Format

API keys are random alphanumeric strings. They are stored as SHA-256 hashes in the database — the raw key is only shown once at creation time. Keep it secure.

sha3 follows the Google JSON Style Guide. Success and failure are indicated by HTTP status codes — there is no top-level success or ok field.

✅ Success (HTTP 2xx)

json
{
  "data": {
    "order_id": "abc-123",
    "status": "pending"
  }
}

❌ Error (HTTP 4xx / 5xx)

json
{
  "error": {
    "code": "INVALID_AMOUNT",
    "message": "Amount must be positive"
  }
}

• Always check HTTP status code first: 2xx = success, 4xx/5xx = error

error.code is a machine-readable string constant (e.g. INVALID_CURRENCY) — use it for programmatic error handling

error.message is human-readable, may change — do not match on it

• Amounts are always decimal strings (e.g. "10.000000"), never floats

EnvironmentAPI (Gateway)
Productionhttps://api.sha3.net

API (Gateway) — single public entry point for all /v1/pay/ endpoints. Production sits behind CloudFront for edge caching and DDoS protection.

Portal API is an internal service (no public ingress). All portal routes go through the Gateway at /v1/portal/.

Deposit widgets use deposit widget sessions and permanent account-bound addresses. Orders remain a separate API resource.

Sandbox is not exposed as a separate public environment today. Chain-backed tests require a real observed transfer on the selected production network, so start with the smallest safe stablecoin amount.

These endpoints require the X-API-Key header. All payment endpoints use the /v1/pay/ prefix.

Order amounts are denominated in the declared currency. amount: "10.00" with currency: "ETH" means 10 ETH; currency: "USD" uses the settled deposit gross USD valuation. The current order API does not lock fiat quotes or apply FX/slippage logic. Settlement requires one matching deposit whose gross token or USD value meets or exceeds the order amount; partial deposits are credited independently but do not combine to fulfill an order.

POST/v1/pay/orders🔐 API Key

Create a token- or USD-denominated order record. Orders do not create or host the deposit widget.

Request Body

ParameterTypeRequiredDescription
amountstringRequiredOrder target as a decimal string in the declared cryptocurrency unit, or in USD when currency is USD.
currencystringRequiredSupported cryptocurrency code returned by GET /v1/currencies, or USD for gross USD valuation.
descriptionstringOptionalMerchant-defined order description
metadataobjectOptionalArbitrary key-value metadata (your internal order ID, user info, etc.). Returned by the order API; not included in the current order.paid webhook payload.
redirect_urlstringOptionalURL to redirect after successful payment
cancel_urlstringOptionalURL to redirect on cancellation
bash
curl -X POST https://api.sha3.net/v1/pay/orders \
  -H "X-API-Key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": "10.00",
    "currency": "USDT_TRC20",
    "description": "Premium Plan",
    "metadata": {
      "merchant_order_id": "INV-10001",
      "customer_id": "cus_123"
    },
    "redirect_url": "https://yoursite.com/success",
    "cancel_url": "https://yoursite.com/cancel"
  }'

Response

json
{
  "data": {
    "order_id": "ord_550e8400-e29b-41d4-a716-446655440000",
    "expires_at": "2026-02-25T12:00:00Z"
  }
}
GET/v1/pay/orders/{order_id}🔐 API Key

Get order details by ID.

bash
curl https://api.sha3.net/v1/pay/orders/ord_550e8400-e29b-41d4-a716-446655440000 \
  -H "X-API-Key: your-api-key"

Response

json
{
  "data": {
    "order_id": "ord_550e8400-e29b-41d4-a716-446655440000",
    "amount": "10.00",
    "currency": "USDT_TRC20",
    "status": "pending",
    "selected_crypto": null,
    "deposit_address": null,
    "deposit_id": null,
    "description": "Premium Plan",
    "metadata": {
      "merchant_order_id": "INV-10001",
      "customer_id": "cus_123"
    },
    "redirect_url": "https://yoursite.com/success",
    "cancel_url": "https://yoursite.com/cancel",
    "expires_at": "2026-02-25T12:00:00Z",
    "created_at": "2026-02-25T11:00:00Z",
    "updated_at": "2026-02-25T11:00:00Z"
  }
}
GET/v1/pay/orders🔐 API Key

List orders for the authenticated merchant.

ParameterTypeRequiredDescription
statusstringOptionalFilter by status: pending, paid, expired
limitintegerOptionalMax results (default: 20, max: 100)
offsetintegerOptionalPagination offset (default: 0)
POST/v1/pay/orders/{order_id}/select🔐 API Key

Select the cryptocurrency and allocate the order deposit address. Orders do not use the deposit widget UI.

ParameterTypeRequiredDescription
currencystringRequiredSupported crypto currency code (e.g. "USDT_TRC20", "ETH"). Selection allocates an address; it does not quote fiat or change the stored order amount.

Response

json
{
  "data": {
    "order_id": "ord_550e8400-...",
    "status": "pending",
    "selected_crypto": "USDT_TRC20",
    "deposit_address": "TXqH4v...",
    ...
  }
}
GET/v1/pay/deposit-address🔐 API Key

Get or allocate a permanent deposit address for a user. The same user always gets the same address (idempotent). Addresses are assigned from the address pool or generated on-demand via the wallet service.

ParameterTypeRequiredDescription
user_idstringRequiredUnique user identifier in your system
currencystringRequiredCurrency code (e.g. "USDT_TRC20", "ETH")
bash
curl "https://api.sha3.net/v1/pay/deposit-address?user_id=user123&currency=USDT_TRC20" \
  -H "X-API-Key: your-api-key"

Response

json
{
  "data": {
    "address": "TXqH4v...",
    "currency": "USDT_TRC20",
    "network": "tron",
    "chain_id": 728126428,
    "user_id": "user123",
    "created_at": "2026-02-25T10:30:00Z"
  }
}
GET/v1/pay/deposits🔐 API Key

List deposits for the authenticated merchant.

ParameterTypeRequiredDescription
user_idstringOptionalFilter by user ID
limitintegerOptionalMax results (default: 20, max: 100)
offsetintegerOptionalPagination offset (default: 0)

Response

json
{
  "data": {
    "deposits": [
      {
        "deposit_id": "dep_abc123",
        "user_id": "user123",
        "address": "TXqH4v...",
        "currency": "USDT_TRC20",
        "chain_id": 728126428,
        "tx_hash": "0xabc...",
        "amount": "100.000000",
        "fee_amount": "1.000000",
        "net_amount": "99.000000",
        "status": "settled",
        "created_at": "2026-02-25T10:00:00Z",
        "confirmed_at": "2026-02-25T10:05:00Z",
        "settled_at": "2026-02-25T10:05:10Z"
      }
    ],
    "total": 1,
    "limit": 20,
    "offset": 0
  }
}
GET/v1/pay/deposit/{deposit_id}🔐 API Key

Get a specific deposit by ID.

GET/v1/pay/balance🔐 API Key

Get the merchant's balance across all currencies. Available is spendable; frozen is held pending withdrawal completion.

json
{
  "data": {
    "balances": [
      { "currency": "USDT_TRC20", "chain_id": 728126428, "available": "1250.50", "frozen": "0.00" },
      { "currency": "ETH", "chain_id": 1, "available": "0.85", "frozen": "0.10" }
    ]
  }
}
POST/v1/pay/withdrawals/quote🔐 API Key

Quote the withdrawal fee for a gross debit amount. The quote is indicative unless you pass the returned fee_amount as expected_fee_amount when creating the withdrawal.

Request Body

ParameterTypeRequiredDescription
chain_idintegerRequiredTarget blockchain chain ID (e.g. 728126428 for Tron, 1 for Ethereum)
token_addressstringOptionalToken contract address. Omit or set null for native coins (ETH, TRX)
amountstringRequiredMerchant gross debit amount as a plain decimal string in token units. Actual on-chain transfer amount is amount - fee_amount.
bash
curl -X POST https://api.sha3.net/v1/pay/withdrawals/quote \
  -H "X-API-Key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "chain_id": 728126428,
    "token_address": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
    "amount": "100.000000"
  }'

Response

json
{
  "data": {
    "currency": "USDT_TRC20",
    "amount": "100.000000",
    "fee_amount": "0.100000"
  }
}
POST/v1/pay/withdrawals🔐 API Key

Create a withdrawal request. amount is the merchant gross debit amount and is immediately frozen from available balance; the actual on-chain transfer amount is amount - fee_amount. Withdrawals within configured limits auto-approve; larger amounts stay pending until signer approval, then move through broadcasting → processing → completed.

Request Body

ParameterTypeRequiredDescription
to_addressstringRequiredDestination wallet address (EIP-55 checksum for EVM chains)
chain_idintegerRequiredTarget blockchain chain ID (e.g. 728126428 for Tron, 1 for Ethereum)
token_addressstringOptionalToken contract address. Omit or set null for native coins (ETH, TRX)
amountstringRequiredMerchant gross debit amount as a plain decimal string in token units. Actual on-chain transfer amount is amount - fee_amount. Scientific notation and wei-style smallest-unit integers are not accepted.
expected_fee_amountstringOptionalOptional fee guard from the quote endpoint. If the current fee differs, creation fails with WITHDRAWAL_FEE_CHANGED.
client_idstringOptionalOptional merchant idempotency key. Reuse the same client_id when retrying the same withdrawal request.
bash
curl -X POST https://api.sha3.net/v1/pay/withdrawals \
  -H "X-API-Key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "to_address": "TXqH4v...",
    "chain_id": 728126428,
    "token_address": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
    "amount": "100.000000",
    "expected_fee_amount": "0.100000",
    "client_id": "merchant_withdrawal_1001"
  }'

Response

json
{
  "data": {
    "id": 42,
    "to_address": "TXqH4v...",
    "chain_id": 728126428,
    "token_address": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
    "currency": "USDT_TRC20",
    "client_id": "merchant_withdrawal_1001",
    "amount": "100.000000",
    "fee_amount": "0.100000",
    "status": "pending",
    "tx_hash": null,
    "confirmations": 0,
    "error_msg": null,
    "requested_at": "2026-02-25T10:00:00Z",
    "approved_at": null,
    "completed_at": null,
    "created_at": "2026-02-25T10:00:00Z"
  }
}

⚠️ Withdrawals exceeding your account's single-tx or daily limits stay pending until approval is submitted through POST /v1/withdrawals/{id}/approve with a signer key. Approved withdrawals then progress through broadcasting and processing before completion. If the platform cannot determine the broadcast outcome, the frozen balance remains held while the sha3 operations team verifies the chain result.

GET/v1/pay/withdrawals🔐 API Key

List withdrawals for the authenticated merchant.

ParameterTypeRequiredDescription
statusstringOptionalFilter by merchant-visible status: pending, approved, broadcasting, processing, completed, failed
searchstringOptionalSearch by withdrawal ID, destination address, tx hash, or client_id prefix
limitintegerOptionalMax results (default: 20, max: 100)
offsetintegerOptionalPagination offset (default: 0)

Response

json
{
  "data": {
    "withdrawals": [
      {
        "id": 42,
        "to_address": "TXqH4v...",
        "chain_id": 728126428,
        "token_address": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
        "currency": "USDT_TRC20",
        "client_id": "merchant_withdrawal_1001",
        "amount": "100.000000",
        "fee_amount": "0.100000",
        "status": "processing",
        "tx_hash": "f4b8...",
        "confirmations": 12,
        "error_msg": null,
        "requested_at": "2026-02-25T10:00:00Z",
        "approved_at": "2026-02-25T10:00:04Z",
        "completed_at": null,
        "created_at": "2026-02-25T10:00:00Z"
      }
    ],
    "total": 10,
    "limit": 20,
    "offset": 0
  }
}

In each withdrawal record, amount is the total balance debit and fee_amount is deducted from that amount before the on-chain transfer.

GET/v1/pay/withdrawals/{id}🔐 API Key

Get a specific withdrawal by numeric ID. The response uses the same record shape as list withdrawals.

json
{
  "data": {
    "id": 42,
    "to_address": "TXqH4v...",
    "chain_id": 728126428,
    "token_address": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t",
    "currency": "USDT_TRC20",
    "client_id": "merchant_withdrawal_1001",
    "amount": "100.000000",
    "fee_amount": "0.100000",
    "status": "processing",
    "tx_hash": "f4b8...",
    "confirmations": 12,
    "error_msg": null,
    "requested_at": "2026-02-25T10:00:00Z",
    "approved_at": "2026-02-25T10:00:04Z",
    "completed_at": null,
    "created_at": "2026-02-25T10:00:00Z"
  }
}
POST/v1/withdrawals/{id}/approve🔐 Signer Key

Approve or reject a pending withdrawal using a signer key. This route is only for configured signers and only works while the withdrawal is still pending.

Required Header

http
X-Signer-Key: sk_live_xxx

Request Body

ParameterTypeRequiredDescription
actionstringRequiredEither approve or reject
commentstringOptionalOptional reviewer comment stored with the approval record
chain_idintegerRequiredChain id used when resolving the applicable multisig policy
token_addressstringOptionalToken contract address for token withdrawals; omit or null for native assets
bash
curl -X POST https://api.sha3.net/v1/withdrawals/42/approve \
  -H "X-Signer-Key: your-signer-key" \
  -H "Content-Type: application/json" \
  -d '{
    "action": "approve",
    "comment": "Within treasury policy",
    "chain_id": 728126428,
    "token_address": "TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t"
  }'

Rejecting a pending withdrawal moves it to failed and refunds the frozen balance back to available.

Create the deposit widget session on your server, return only its ID and client secret to your browser, and mount the widget with window.Sha3Deposit. Each settled transfer is credited independently and delivered through deposit.settled; the optional target is progress guidance, not an order balance.

Environment Variables

ParameterTypeRequiredDescription
SHA3_API_BASE_URLstringOptionalGateway API base URL. Production: https://api.sha3.net
SHA3_FRONTEND_BASE_URLstringOptionalFrontend base URL hosting /widget.js and deposit pages. Production: https://www.sha3.net
SHA3_API_KEYstringRequiredMerchant API key. Keep it server-side only.
SHA3_IPN_SECRETstringRequiredIPN secret from webhook configuration. Keep it server-side only.
PUBLIC_BASE_URLstringRequiredPublic HTTPS base URL for your app and the widget parent origin.

Install and Run

bash
npm init -y
npm install express

export SHA3_API_BASE_URL=https://api.sha3.net
export SHA3_FRONTEND_BASE_URL=https://www.sha3.net
export SHA3_API_KEY=your-api-key
export SHA3_IPN_SECRET=your-ipn-secret
export PUBLIC_BASE_URL=https://merchant.example.com

node server.mjs

Complete Node.js Server

javascript
import crypto from 'node:crypto';
import express from 'express';

const API_BASE_URL = process.env.SHA3_API_BASE_URL || 'https://api.sha3.net';
const FRONTEND_BASE_URL = process.env.SHA3_FRONTEND_BASE_URL || 'https://www.sha3.net';
const PUBLIC_BASE_URL = required('PUBLIC_BASE_URL');
const API_KEY = required('SHA3_API_KEY');
const IPN_SECRET = required('SHA3_IPN_SECRET');
const PORT = Number(process.env.PORT || 3000);

const app = express();
const seenNonces = new Set();
const processedEvents = new Set();

function required(name) {
  const value = process.env[name];
  if (!value) {
    throw new Error(name + ' is required');
  }
  return value;
}

async function sha3Fetch(path, options = {}) {
  const response = await fetch(API_BASE_URL + path, {
    ...options,
    headers: {
      'X-API-Key': API_KEY,
      'Content-Type': 'application/json',
      ...(options.headers || {}),
    },
  });

  const body = await response.json().catch(() => ({}));
  if (!response.ok) {
    throw new Error('sha3 ' + response.status + ': ' + JSON.stringify(body));
  }
  return body.data;
}

function verifySignature(rawBody, timestamp, nonce, signature) {
  if (!timestamp || !nonce || !signature) return false;

  const ageSeconds = Math.abs(Date.now() / 1000 - Number(timestamp));
  if (!Number.isFinite(ageSeconds) || ageSeconds > 300) return false;
  if (seenNonces.has(nonce)) return false;

  const input = timestamp + '.' + nonce + '.' + rawBody;
  const expected = crypto
    .createHmac('sha512', IPN_SECRET)
    .update(input, 'utf8')
    .digest('hex');

  const actualBuffer = Buffer.from(signature, 'hex');
  const expectedBuffer = Buffer.from(expected, 'hex');
  if (actualBuffer.length !== expectedBuffer.length) return false;

  const valid = crypto.timingSafeEqual(actualBuffer, expectedBuffer);
  if (valid) seenNonces.add(nonce);
  return valid;
}

function eventKey(event) {
  const reference = event.deposit_id || event.withdrawal_id || event.user_id;
  return event.event + ':' + reference;
}

function originOf(url) {
  return new URL(url).origin;
}

async function recordSettledDeposit(externalUserId, event) {
  // Replace this with your durable notification or reconciliation workflow.
  console.log('Settled deposit for', externalUserId, event);
}

app.post('/sha3/webhook', express.raw({ type: 'application/json' }), async (req, res) => {
  const rawBody = req.body.toString('utf8');
  const timestamp = req.get('x-timestamp');
  const nonce = req.get('x-nonce');
  const signature = req.get('x-signature');
  const eventHeader = req.get('x-event');

  if (!verifySignature(rawBody, timestamp, nonce, signature)) {
    return res.status(401).send('invalid signature');
  }

  let event;
  try {
    event = JSON.parse(rawBody);
  } catch {
    return res.status(400).send('invalid json');
  }

  if (eventHeader !== event.event) {
    return res.status(400).send('event mismatch');
  }

  const key = eventKey(event);
  if (processedEvents.has(key)) {
    return res.status(200).send('duplicate');
  }

  if (event.event === 'deposit.settled') {
    await recordSettledDeposit(event.user_id, event);
  }

  processedEvents.add(key);
  res.status(200).send('ok');
});

app.use(express.json());

app.post('/deposit-widget-sessions', async (req, res, next) => {
  try {
    const currencies = req.body.allowed_currencies || ['USDT_TRC20'];
    const data = await sha3Fetch('/v1/pay/deposit-widget-sessions', {
      method: 'POST',
      body: JSON.stringify({
        external_user_id: req.body.external_user_id,
        mode: 'embedded',
        allowed_currencies: currencies,
        allowed_origins: [originOf(PUBLIC_BASE_URL)],
        target_amount: req.body.target_amount,
        target_currency: req.body.target_currency,
        target_editable: Boolean(req.body.target_editable),
        tolerance_bps: req.body.tolerance_bps || 0,
        metadata: req.body.metadata,
      }),
    });

    res.json({
      deposit_widget_session_id: data.deposit_widget_session_id,
      client_secret: data.client_secret,
      hosted_url:
        FRONTEND_BASE_URL +
        data.hosted_url +
        '#client_secret=' +
        encodeURIComponent(data.client_secret),
      widget_script_url: FRONTEND_BASE_URL + '/widget.js',
      expires_at: data.expires_at,
    });
  } catch (error) {
    next(error);
  }
});

app.get('/deposits/:depositId', async (req, res, next) => {
  try {
    const deposit = await sha3Fetch('/v1/pay/deposits/' + encodeURIComponent(req.params.depositId));
    res.json(deposit);
  } catch (error) {
    next(error);
  }
});

app.use((error, _req, res, _next) => {
  console.error(error);
  res.status(500).json({ error: 'internal_error' });
});

app.listen(PORT, () => {
  console.log('Merchant integration listening on :' + PORT);
});

The in-memory nonce and event sets keep the example compact. In production, store webhook nonces and processed event keys in a durable database or Redis so retries and restarts stay idempotent.

Create a Deposit Widget Session

bash
curl -X POST http://localhost:3000/deposit-widget-sessions \
  -H "Content-Type: application/json" \
  -d '{
    "external_user_id": "customer_123",
    "allowed_currencies": ["USDT_TRC20", "USDC_ETH"],
    "target_amount": "100.00",
    "target_currency": "USDT_TRC20",
    "target_editable": true,
    "tolerance_bps": 50
  }'

# Mount the returned deposit_widget_session_id and client_secret with the widget.
# For hosted redirect, open hosted_url in the customer's browser.
# Listen for deposit.settled on your webhook for every settled transfer.

Mount the Widget

html
<script src="https://www.sha3.net/widget.js"></script>
<div id="sha3-deposit"></div>
<script>
async function startDeposit() {
  const response = await fetch('/deposit-widget-sessions', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      external_user_id: 'customer_123',
      allowed_currencies: ['USDT_TRC20'],
      target_amount: '100.00',
      target_currency: 'USDT_TRC20'
    })
  });
  const session = await response.json();

  window.Sha3Deposit.mount('#sha3-deposit', {
    sessionId: session.deposit_widget_session_id,
    clientSecret: session.client_secret,
    baseUrl: 'https://www.sha3.net',
    onProgress: event => console.log(event.progress),
    onSettled: event => console.log(event.deposit),
    onTargetReached: event => console.log(event.progress)
  });
}

startDeposit();
</script>

The API key stays on your server. The browser receives only the short-lived widget client secret. The deposit address remains bound to the external user after the display session expires.

Webhook Smoke Test Without a Chain Transfer

Before sending chain funds, verify that your receiver accepts a correctly signed payload and rejects bad signatures. This smoke test uses a deposit event so it does not trigger order fulfillment.

bash
node - <<'NODE'
const crypto = require('node:crypto');

const secret = process.env.SHA3_IPN_SECRET;
const body = JSON.stringify({
  event: 'deposit.settled',
  deposit_id: 'dep_test',
  merchant_id: 1,
  user_id: 'smoke-test-user',
  amount: '10.000000',
  fee_amount: '0.100000',
  net_amount: '9.900000',
  status: 'settled',
  timestamp: new Date().toISOString(),
});
const timestamp = Math.floor(Date.now() / 1000).toString();
const nonce = crypto.randomUUID();
const signature = crypto
  .createHmac('sha512', secret)
  .update(timestamp + '.' + nonce + '.' + body)
  .digest('hex');

console.log('curl -X POST http://localhost:3000/sha3/webhook \\');
console.log('  -H "Content-Type: application/json" \\');
console.log('  -H "X-Timestamp: ' + timestamp + '" \\');
console.log('  -H "X-Nonce: ' + nonce + '" \\');
console.log('  -H "X-Signature: ' + signature + '" \\');
console.log("  --data '" + body.replace(/'/g, "'\\''") + "'");
NODE

Chain-Backed Test Checklist

1

Configure Deposit Webhook

Enable the deposit webhook endpoint, set it to https://your-public-domain/sha3/webhook, and save the deposit IPN secret. Local servers need an HTTPS tunnel.

2

Discover Currency

Call GET /v1/currencies and choose a currency supported by your test wallet and target chain.

3

Create Widget Session

Call your /deposit-widget-sessions endpoint and mount the returned session with window.Sha3Deposit.

4

Send Chain Deposit

Send any positive amount to the permanent account-bound address on the selected chain. Use the smallest safe amount for production networks.

5

Wait for Confirmations

The chain syncer must detect the transfer and wait for the required confirmations before settlement.

6

Receive deposit.settled

Your webhook receives one deposit.settled event for each settled transfer. Use payload.user_id as the external user binding.

7

Verify Progress

The widget status endpoint reports cumulative settled progress and recent independent deposits for this session.

Use live funds only after the merchant account, deposit webhook endpoint, and event idempotency are verified. There is no mock settlement callback from the production API; real deposit.settled delivery requires an observed on-chain transfer.