# CoinsSend Payments documentation --- Source: https://docs.coinssend.com/README.md # CoinsSend API Documentation This documentation covers the verified public merchant integration and checkout contract. Start with the machine-readable [OpenAPI 3.1 document](https://docs.coinssend.com/openapi.json) for agent/tool integration and use the narrative guides for operational detail. ## Guides - [Supported Coins & Networks](https://docs.coinssend.com/supported-coins.md) - [Getting Started](https://docs.coinssend.com/getting-started.md) - [Authentication and signature vectors](https://docs.coinssend.com/authentication.md) - [API Reference](https://docs.coinssend.com/api-reference.md) - [Invoices](https://docs.coinssend.com/invoices.md) - [Withdrawals](https://docs.coinssend.com/withdrawals.md) - [Static Wallets](https://docs.coinssend.com/static-wallets.md) - [Merchant balances and fees](https://docs.coinssend.com/merchants.md) - [Coin rates and live catalog](https://docs.coinssend.com/coin-rates.md) - [Webhooks](https://docs.coinssend.com/webhooks.md) - [Error Handling](https://docs.coinssend.com/error-handling.md) - [Rate Limits and Safe Retries](https://docs.coinssend.com/rate-limits.md) - [Server-side examples](https://docs.coinssend.com/examples/README.md) - [AI integration prompts](https://docs.coinssend.com/ai-integration-prompts.md) ## Public Allowlist Authenticated merchant operations: - `POST /v1/invoices` - `POST /v1/withdrawals` - `GET /v1/merchants/balances` - `GET /v1/merchants/fees` - `POST /v1/wallet-address` Public discovery and checkout operations: - `GET /v1/get-coin-rate` - `GET /v1/coins-and-fee` - `GET /v1/invoices/{invoiceCode}` - `POST /v1/invoices/{invoiceCode}/provider-card-orders` - `GET /v1/wallet-addresses/{walletAddress}/qr` Only the operations listed above are part of the supported public integration contract. ## Authentication Summary Merchant operations require `Merchant` and `Sign`. Withdrawal submission also requires a fresh `Timestamp` (`X-Timestamp` is accepted as an alias). The request signature uses the server-canonical JSON body, not arbitrary raw JSON formatting: ```text canonical_body = PHP-compatible canonical_json(request_object) legacy Sign = md5(base64(canonical_body) + API_KEY) withdrawal Sign = hmac_sha256(base64(canonical_body) + "." + Timestamp, API_KEY) ``` Generate one canonical string, sign it, and send it as the request body. Verify client implementations with [`signature-test-vectors.json`](https://docs.coinssend.com/signature-test-vectors.json). Keep API keys in trusted server-side secrets. Webhook signing is deliberately different: verify `X-Signature` as HMAC-SHA256 over the exact raw webhook body before parsing it. ## Live Asset Data See [Supported Coins & Networks](https://docs.coinssend.com/supported-coins.md) for the coin/network tables, API identifiers, and guidance on displaying payment options. Do not copy a static coin/network enum from examples. Use `GET /v1/coins-and-fee` for discovery and handle validation from the target write endpoint as authoritative. Availability, limits, decimals, fees, rates, and provider-card capability can change. ## Response Shapes Most successful responses use: ```json { "data": {}, "status": "success" } ``` Endpoint errors usually use `status` and `message`; validation errors add `errors`; authentication errors return `{"error":"Reason"}`. Symbolic error labels in integration code are client-side categories, not guaranteed wire fields. ## Idempotency and Retries The public write API does not accept a general `Idempotency-Key` header. - Invoice `order_id` uniqueness returns `409` and does not replay the original response. - Withdrawals are not idempotent; timestamp freshness only limits stale replay. - Static-wallet creation is not idempotent. - Provider-card creation only reuses an already-active order. - Withdrawal webhook deliveries currently include a stable `X-Idempotency-Key`; other webhook families do not guarantee it. Do not blindly retry a write after an unknown outcome. See [Rate Limits and Safe Retries](https://docs.coinssend.com/rate-limits.md). ## Rate-Limit Scope The published API contract guarantees one specific limit: 100 successful wallet creations per merchant in 60 seconds. Additional limits may apply, so clients must also handle any HTTP `429` response and honor `Retry-After` when present. --- Source: https://docs.coinssend.com/supported-coins.md # Supported Coins & Networks Use this page to see which cryptocurrencies and blockchain networks CoinsSend supports, choose labels for your interface, and find the codes to send in API requests. ## Supported assets at a glance The public catalog was checked on **September 17, 2026 (UTC)**: **9 coins, 7 networks, and 18 coin/network pairs**. This is a dated reference; load the [live catalog](https://api.coinssend.com/v1/coins-and-fee) to build your current payment or withdrawal choices. | Network display name | API `network` code | Coins in the catalog | | ------------------------ | ------------------ | ------------------------------------ | | Bitcoin | `bitcoin` | BTC | | Ethereum (ERC-20) | `eth` | ETH, USDT, USDC | | TRON (TRC-20) | `tron` | TRX, USDT | | BNB Smart Chain (BEP-20) | `bsc` | BNB, USDT, USDC | | Polygon PoS | `polygon` | POL, USDT, USDC | | TON (Jetton) | `ton` | TON, USDT | | Arbitrum One | `arbitrum` | ARB, ETH (deposits only), USDT, USDC | At that check, deposits and withdrawals were enabled for all listed pairs except **ETH on Arbitrum One**, where deposits were enabled and withdrawals were disabled. Check the current operation flags before enabling a choice. The coin codes for requests are `btc`, `eth`, `trx`, `bnb`, `pol`, `ton`, `arb`, `usdt`, and `usdc`. Keep these values dynamic in your integration; new assets may be added and existing pairs may become unavailable. ## Coin codes, network codes, and display labels A coin and its network form one payment option. **USDT on TRON and USDT on Ethereum are different options**: always display both the coin and the network when asking a customer where to send funds. | Purpose | Value to use | Example | | ---------------------------- | -------------------------------------- | --------------- | | Display the coin | Catalog `coin.label` | `USDT` | | Display the network | Catalog `networks[].title` | `TRON (TRC-20)` | | Send `coin` in a request | Lowercase catalog `coin.symbol` | `usdt` | | Send `network` in a request | Lowercase catalog `networks[].network` | `tron` | | Identify a choice in your UI | Both normalized codes | `usdt:tron` | Use `eth` for the Ethereum network and `bitcoin` for the Bitcoin network. Display names and token-standard labels such as `ERC-20`, `TRC-20`, and `BEP-20` are not API network codes. The native Polygon coin code is `pol`. Do not combine every coin with every network or infer the network from an address alone. ## Load the live catalog Both discovery endpoints are public and require no API key, `Merchant`, or `Sign` header: ```bash curl --fail --show-error https://api.coinssend.com/v1/coins-and-fee curl --fail --show-error https://api.coinssend.com/v1/get-coin-rate ``` | Endpoint | How to use it | | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | `GET /v1/coins-and-fee` | Read `data.coins` for the available coin/network pairs, display labels, network fees, service-fee percentages, and minimum withdrawals. | | `GET /v1/get-coin-rate` | Match `data[].coin.symbol` and `networks[].network` for `deposit_enabled`, `withdraw_enabled`, amount limits, decimals, and exchange rates. | The catalog includes pairs enabled for deposits, withdrawals, **or both**; it does not return the individual operation flags. A `min_withdrawal` or `network_fee` value by itself does not mean withdrawals are enabled. Use the catalog as the starting list, then match the rate metadata by the lowercase **coin/network pair**. For receiving payments, require `deposit_enabled === true`; for withdrawals, require `withdraw_enabled === true`. The rates response can contain coins with no available networks and can omit assets without rate data. For example, at the check above SOL had rate data but no networks and was absent from the catalog. A price or quote alone is not evidence that an asset is available for payments. If metadata for a catalog pair is missing, show its availability as unknown and refresh before enabling the operation. ### JavaScript: build coin and network choices This example only reads public data. It keeps catalog entries with missing metadata visible as unavailable choices, without assuming that they support either operation. ```javascript const api = 'https://api.coinssend.com'; async function getData(path) { const response = await fetch(`${api}${path}`); if (!response.ok) throw new Error(`Discovery failed: ${response.status}`); const body = await response.json(); if (body.status !== 'success') throw new Error('Discovery failed'); return body.data; } const [catalog, rates] = await Promise.all([ getData('/v1/coins-and-fee'), getData('/v1/get-coin-rate'), ]); const metadata = new Map(rates.flatMap((entry) => entry.networks.map((network) => [ `${entry.coin.symbol.toLowerCase()}:${network.network.toLowerCase()}`, network, ]) )); const choices = catalog.coins.flatMap((entry) => entry.networks.map((network) => { const coin = entry.coin.symbol.toLowerCase(); const code = network.network.toLowerCase(); const id = `${coin}:${code}`; const details = metadata.get(id); return { id, coin, network: code, label: `${entry.coin.label} — ${network.title}`, availabilityKnown: details !== undefined, depositEnabled: details?.deposit_enabled === true, withdrawEnabled: details?.withdraw_enabled === true, }; }) ); const depositChoices = choices.filter((choice) => choice.depositEnabled); const withdrawalChoices = choices.filter((choice) => choice.withdrawEnabled); ``` If discovery fails, show a loading/error state with a retry option. Do not replace a failed response with a hardcoded list of enabled payment methods. The catalog advertises `Cache-Control: public, max-age=900` (15 minutes); respect that cache window and handle changes between discovery and submission. ## Use a selected pair in requests | Operation | Fields | | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Create an invoice](https://docs.coinssend.com/invoices.md#create-invoice) | Use lowercase `coin` and `network` to preselect a pair. `allowed_coins` is an array of lowercase coin codes, not network codes or `coin:network` identifiers. | | [Create a static wallet](https://docs.coinssend.com/static-wallets.md#create-wallet-address) | Send the selected lowercase `coin` and `network` with the other required wallet fields. | | [Create a withdrawal](https://docs.coinssend.com/withdrawals.md#initiate-withdrawal) | Send the selected lowercase `coin` and `network`; check withdrawal availability and limits for that pair. | For example, a selected USDT payment on TRON supplies these fields (this is only the pair, not a complete request): ```json { "coin": "usdt", "network": "tron" } ``` Discovery is a UI aid. The target endpoint makes the final decision on pair and amount validation; handle HTTP `422`, refresh the choices, and explain the validation message to the customer. Do not silently switch the network or retry a write automatically. ## Fees, limits, and precision Read current values from the discovery responses; do not copy the examples into permanent fee or limit tables. Keep monetary values as decimal strings, and use the selected pair's precision rather than assuming one decimal count for a coin across every network. The public catalog's service fee is not a merchant-specific fee quote. For your merchant's settings, use [Merchant Fees](https://docs.coinssend.com/merchants.md#get-merchant-fees). See [Coin Rates API](https://docs.coinssend.com/coin-rates.md) for the complete discovery response fields. --- Source: https://docs.coinssend.com/getting-started.md # Getting Started with CoinsSend API

Welcome to the CoinsSend API documentation. This guide will help you integrate cryptocurrency payments, wallets, and withdrawals into your application with our simple yet powerful API. Follow these steps to get started quickly.

## Integration Overview
1

Create a Merchant Account

Sign up for a merchant account to get your unique merchant ID and API credentials. Our simple onboarding process helps you get started in minutes.

Create Account
2

Generate API Keys

Create API keys with specific permissions for invoice creation, wallet management and withdrawals. Our granular permissions system ensures you have the right security controls.

Learn More
3

Implement Payment Flow

Choose between invoices for one-time payments or static wallets for recurring transactions. Both options provide seamless integration with your existing systems.

View Invoices API View Static Wallets API
4

Set Up Withdrawals (Optional)

Configure cryptocurrency withdrawals to allow your merchants to transfer funds to external wallets securely with minimal effort.

Withdrawals Guide
5

Set Up Webhooks

Configure webhooks to receive real-time payment notifications and transaction updates. Our robust event system keeps your application in sync with invoice status changes and static wallet transactions.

Webhook Guide
## Account Setup ### Create Merchant Account 1. Go to [CoinsSend Sign Up](https://app.coinssend.com/sign-up) 2. Complete the registration form with your business information (no KYC required) 3. Verify your email address ### Generate API Credentials After setting up your merchant account: 1. Log in to your merchant dashboard 2. Create merchant 3. Navigate to Merchant Settings 4. Note your **Merchant ID** (you'll need this for all API requests) 5. Find your API key (generated automatically when the merchant is created) - withdrawal permissions are configured in merchant settings
Security Best Practice: Always store your API keys securely in environment variables or a secure key vault. Never expose API keys in client-side code, public repositories, or include them directly in your application's source code.
## Payment Integration Options Start with [Supported Coins & Networks](https://docs.coinssend.com/supported-coins.md) to see the available assets, network identifiers, and how to keep payment options current. CoinsSend offers two primary methods for accepting cryptocurrency payments:

Invoices

Generate single-use payment links for specific order amounts. Ideal for e-commerce and one-time payments.

  • Fixed payment amounts in USD with automatic crypto conversion
  • Stablecoin support including USDT and other stablecoins
  • 24-hour expiration with automatic status updates
  • Webhook notifications for payment status updates
View Invoices API

Static Wallets

Permanent cryptocurrency addresses for receiving payments. Great for donations or recurring clients.

  • Dedicated blockchain addresses for each supported network
  • Multiple networks for stablecoins including TRON and BSC
  • Custom labels for better organization
  • Instant webhook notifications for all received transactions
View Static Wallets API
## Code Example: Create an Invoice Here's a quick server-side example. Each language generates the canonical JSON body from [Authentication](https://docs.coinssend.com/authentication.md), signs it, and sends the same string. Never put the merchant API key in browser code.
```php 'test_order_' . time(), 'amount' => '100.50', 'is_customer_fee' => true ]; // Generate signature $jsonData = json_encode($data, JSON_THROW_ON_ERROR); $base64Data = base64_encode($jsonData); $signature = md5($base64Data . $apiKey); // Create HTTP client $ch = curl_init('https://api.coinssend.com/v1/invoices'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonData); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json', 'Merchant: ' . $merchantId, 'Sign: ' . $signature ]); // Execute request $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); // Process response $result = json_decode($response, true); // Handle result if ($httpCode >= 200 && $httpCode < 300 && $result['status'] === 'success') { echo "Invoice created! Payment URL: {$result['data']['url']}\n"; echo "Invoice code: {$result['data']['code']}\n"; } else { echo "Error: " . json_encode($result) . "\n"; } ``` ```javascript // Replace with your actual credentials const merchantId = 'your_merchant_id'; const apiKey = 'your_api_key'; // Create request data const data = { order_id: `test_order_${Date.now()}`, amount: '100.50', is_customer_fee: true }; // Generate signature const jsonData = JSON.stringify(data) .replace(/\//g, '\\/') .replace(/[\u0080-\uFFFF]/g, character => `\\u${character.charCodeAt(0).toString(16).padStart(4, '0')}` ); const base64Data = Buffer.from(jsonData, 'utf8').toString('base64'); const crypto = require('node:crypto'); const signature = crypto.createHash('md5').update(base64Data + apiKey).digest('hex'); // Make the API request fetch('https://api.coinssend.com/v1/invoices', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Merchant': merchantId, 'Sign': signature }, body: jsonData }) .then(response => response.json()) .then(result => { if (result.status === 'success') { console.log(`Invoice created! Payment URL: ${result.data.url}`); console.log(`Invoice code: ${result.data.code}`); } else { console.error('Error:', result); } }) .catch(error => console.error('API Request failed:', error)); ``` ```python import requests import json import base64 import hashlib import time # Replace with your actual credentials merchant_id = 'your_merchant_id' api_key = 'your_api_key' # Create request data data = { 'order_id': f'test_order_{int(time.time())}', 'amount': '100.50', 'is_customer_fee': True } def canonical_json(payload): encoded = json.dumps( payload, ensure_ascii=False, separators=(',', ':'), allow_nan=False ) escaped = [] for character in encoded: codepoint = ord(character) if character == '/': escaped.append(r'\/') elif codepoint < 0x80: escaped.append(character) elif codepoint <= 0xFFFF: escaped.append(f'\\u{codepoint:04x}') else: codepoint -= 0x10000 escaped.append(f'\\u{0xD800 + (codepoint >> 10):04x}') escaped.append(f'\\u{0xDC00 + (codepoint & 0x3FF):04x}') return ''.join(escaped) # Generate signature from the exact body that will be sent json_data = canonical_json(data) base64_data = base64.b64encode(json_data.encode()).decode() signature = hashlib.md5((base64_data + api_key).encode()).hexdigest() # Make the API request response = requests.post( 'https://api.coinssend.com/v1/invoices', headers={ 'Content-Type': 'application/json', 'Merchant': merchant_id, 'Sign': signature }, data=json_data ) # Process response result = response.json() # Handle result if response.status_code >= 200 and response.status_code < 300 and result['status'] == 'success': print(f"Invoice created! Payment URL: {result['data']['url']}") print(f"Invoice code: {result['data']['code']}") else: print(f"Error: {result}") ```
## Implementing Webhooks To receive real-time payment notifications, set up a webhook endpoint: 1. Create an HTTP endpoint in your application to receive webhook events 2. Configure the merchant callback URL in your dashboard; static-wallet creation may also specify its own `webhook_url` 3. Implement signature verification to validate incoming webhooks 4. Process webhook events asynchronously
Developer Tip: During development, use a webhook testing tool like webhook.site or ngrok to inspect and debug incoming webhook payloads. These tools provide temporary endpoints that you can use to test your webhook integration before setting up your production endpoint.
See the [Webhooks documentation](https://docs.coinssend.com/webhooks.md) for details on webhook formats and best practices. ## Next Steps Now that you have the basics set up, you can:
📃

Create Invoices

Generate payment links for specific amounts with multiple crypto options

👛

Set Up Static Wallets

Create permanent cryptocurrency addresses for recurring payments

📤

Process Withdrawals

Send funds to external wallets securely with automatic verification

🔔

Configure Webhooks

Receive real-time payment notifications for all transaction events

## Build with an AI assistant
🧠

AI Integration Prompts

Use this guide for full checkout integration prompts covering invoice setup, callbacks, and production hardening.

## Support If you need help with your integration: - Check our [API reference documentation](https://docs.coinssend.com/api-reference.md) - Review common [HTTP errors and troubleshooting](https://docs.coinssend.com/error-handling.md) - Contact our support team at --- Source: https://docs.coinssend.com/QUICKSTART.md # API Quickstart: invoice → webhook This quickstart walks through a minimal, end-to-end integration path: 1. Prepare signature-based API key authentication 2. Create an invoice 3. Redirect buyer to the payment page 4. Verify invoice webhooks *** ## Prerequisites - Base URL: `https://api.coinssend.com` - A merchant ID from your CoinsSend dashboard - A merchant API key for signature auth (`Merchant` + `Sign` headers; withdrawals also require `Timestamp`) References: - [Authentication](https://docs.coinssend.com/authentication.md) - [OpenAPI contract](https://docs.coinssend.com/openapi.json) - [Signature test vectors](https://docs.coinssend.com/signature-test-vectors.json) - [Invoices](https://docs.coinssend.com/invoices.md) - [Webhooks](https://docs.coinssend.com/webhooks.md) > Want a coding assistant to build this flow? Use the full checkout prompt in [AI Integration Prompts](https://docs.coinssend.com/ai-integration-prompts.md) after reviewing these steps. *** ## Step 1) Prepare API key signature auth for merchant endpoints Get your merchant ID and API key from the CoinsSend dashboard before making public API requests. For signed endpoints (for example `POST /v1/invoices`), generate: ```text canonical_body = json_encode(json_decode(request_body)) using PHP defaults Sign = md5(base64(canonical_body) + API_KEY) ``` Withdrawal requests use a stricter timestamped signature instead: ```text Timestamp = current Unix timestamp in seconds Sign = hmac_sha256(base64(canonical_body) + "." + Timestamp, API_KEY) ``` Send `canonical_body` as the request body. PHP's default JSON encoding escapes slashes and non-ASCII characters, so plain `JSON.stringify()` output is not always equivalent. Example in Node.js: ```javascript import { createHash } from 'node:crypto'; function canonicalJson(value) { return JSON.stringify(value) .replace(/\//g, '\\/') .replace(/[\u0080-\uFFFF]/g, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, '0')}` ); } const body = canonicalJson({ order_id: 'order_10001', amount: '100.50', is_customer_fee: true, allow_card_payments: true, success_url: 'https://merchant.example/pay/success', cancel_url: 'https://merchant.example/pay/cancel' }); const sign = createHash('md5') .update(Buffer.from(body).toString('base64') + process.env.COINSSEND_API_KEY) .digest('hex'); ``` Required headers for signed merchant API calls: - `Merchant: ` - `Sign: ` - `Content-Type: application/json` For `POST /v1/withdrawals`, send `Timestamp: ` and set `Sign` to the HMAC-SHA256 value. *** ## Step 2) Create an invoice (API key auth) ```bash BODY='{"order_id":"order_10001","amount":"100.50","is_customer_fee":true,"allow_card_payments":true,"success_url":"https:\/\/merchant.example\/pay\/success","cancel_url":"https:\/\/merchant.example\/pay\/cancel"}' # BODY is canonical PHP-compatible JSON. Compute SIGN from these exact bytes. SIGN='' curl -X POST "https://api.coinssend.com/v1/invoices" \ -H "Content-Type: application/json" \ -H "Merchant: $MERCHANT_ID" \ -H "Sign: $SIGN" \ -d "$BODY" ``` Example response: ```json { "status": "success", "data": { "id": "4e7d786f-a4a6-4a42-b0e5-6f2bb8a5f0f2", "order_id": "order_10001", "url": "https://pay.coinssend.com/invoice_code", "code": "invoice_code", "amount": "100.50", "payer_amount": "100.50", "expired_at": "2026-04-03T12:00:00+00:00", "created_at": "2026-04-02T12:00:00+00:00" } } ``` *** ## Step 3) Send buyer to payment URL Use `data.url` from invoice creation response as the checkout URL. - Typical shape: `https://pay.coinssend.com/{invoice_code}` - `data.code` can also be used with public invoice lookup: `GET /v1/invoices/{invoiceCode}` - Direct crypto payment is always available. Card checkout is shown only when `allow_card_payments` is `true` and the invoice amount or remaining amount is at least `$15.00` / `1500` cents. - `allow_card_payments` and `provider_card` can appear in invoice lookup and payment-page invoice payloads, but they are not part of the `POST /v1/invoices` create response. - Redirects from success or cancel URLs, provider redirects, provider webhooks, and provider status polling are only UX signals or provider hints. Confirm payment from invoice status changes after chain confirmation, usually through signed webhooks. *** ## Step 4) Handle webhook events When invoice/payment state changes, CoinsSend sends signed webhook callbacks to your webhook URL. Common events for this flow: - `invoice.paid` - `invoice.expired` - `aml.rejected.invoice` (when AML blocks invoice deposit) Verify `X-Signature` using your API key and the **raw** request body bytes: ```php $payload = file_get_contents('php://input'); $signature = $_SERVER['HTTP_X_SIGNATURE'] ?? ''; $expected = hash_hmac('sha256', $payload, $apiKey); if (!hash_equals($expected, $signature)) { http_response_code(401); exit('invalid signature'); } ``` ## Next steps - Expand invoice validation and error handling: [Invoices](https://docs.coinssend.com/invoices.md) - Add duplicate-safe processing in your webhook consumer: [Webhooks](https://docs.coinssend.com/webhooks.md) - Do not blindly retry invoice creation: the endpoint has no idempotency-key contract. Reconcile by `order_id` before deciding whether to create again. - Review request signing details: [Authentication](https://docs.coinssend.com/authentication.md) --- Source: https://docs.coinssend.com/ai-integration-prompts.md # AI Integration Prompts Use these copy-paste prompts with your AI coding assistant when you want help integrating the CoinsSend API into a full checkout flow. Each prompt tells the assistant to follow the canonical docs, keep merchant secrets server-side, and verify the work inside your own project. ## Before you ask an AI assistant Read these source-of-truth docs first, then paste the relevant prompt into your coding assistant: - [Getting Started](https://docs.coinssend.com/getting-started.md) - [OpenAPI 3.1](https://docs.coinssend.com/openapi.json) - [Signature Test Vectors](https://docs.coinssend.com/signature-test-vectors.json) - [API Quickstart](https://docs.coinssend.com/QUICKSTART.md) - [Authentication](https://docs.coinssend.com/authentication.md) - [Invoices](https://docs.coinssend.com/invoices.md) - [Webhooks](https://docs.coinssend.com/webhooks.md) - [Error Handling](https://docs.coinssend.com/error-handling.md) - [Rate Limits](https://docs.coinssend.com/rate-limits.md) ## Full checkout integration prompt ```markdown You are helping me add a CoinsSend full checkout integration to my application. Use the canonical CoinsSend docs as the source of truth: - getting-started.md - QUICKSTART.md - authentication.md - invoices.md - webhooks.md - error-handling.md - rate-limits.md - openapi.json - signature-test-vectors.json Implement a server-side checkout flow using these values only after I replace them: - Base API URL: {BASE_URL} - Merchant ID: {MERCHANT_ID} - API key environment variable name: {API_KEY_ENV} - Order ID: {ORDER_ID} - Amount: {AMOUNT} - Success redirect URL: {SUCCESS_URL} - Cancel redirect URL: {CANCEL_URL} - Webhook URL: {WEBHOOK_URL} Requirements: 1. Keep the merchant API key in environment variables or server secrets only. Never put it in frontend code, public code, logs, build output, or browser storage. 2. Create the invoice from server-side code. The frontend may ask my server to start checkout, but it must not sign merchant API requests. 3. Generate the PHP-compatible canonical JSON body from authentication.md, sign exactly md5(base64(canonical_body) + API_KEY), and send that same canonical string as the HTTP body. Verify the implementation against signature-test-vectors.json. 4. Send the required merchant authentication headers from the server request. 5. Include the order ID, amount, success URL, and cancel URL according to the invoice docs. Configure the merchant webhook callback in merchant settings; webhook_url is not an invoice-creation field. 6. Return or redirect the buyer to the invoice payment URL from the invoice creation response. 7. Treat success and cancel redirect URLs as UX signals only. Do not mark the order paid from a redirect. Payment confirmation must come from verified webhooks and persisted invoice status. 8. Persist the local order, CoinsSend invoice ID or code, payment URL, current status, and timestamps needed for reconciliation. 9. Handle invoice.paid as terminal success. Handle invoice.expired and aml.rejected.invoice as terminal failure states. Handle partial as not complete and keep the order awaiting full payment unless my business rules say otherwise. 10. Add a local uniqueness guard for repeated checkout starts. The API returns 409 for a duplicate order_id but does not replay the original response. Separately make webhook processing idempotent. 11. Add user-project tests and checks for signing, invoice creation, redirect URL behavior, status persistence, partial payment handling, expired handling, AML rejection handling, and the rule that frontend code never sees merchant secrets. 12. Run the relevant lint, type, unit, integration, and build checks available in my project, then report the exact commands and results. Do not duplicate whole endpoint references in my code comments or docs. Link back to the canonical CoinsSend docs instead. ``` ## Webhook receiver prompt Use this webhook assistant prompt whenever you need a webhook implementation checklist before code. ```markdown You are helping me implement a CoinsSend webhook receiver. Use the canonical CoinsSend webhooks and authentication docs as the source of truth. Build the receiver in my existing server framework and match my project's routing, persistence, logging, and test patterns. Requirements: 1. Preserve the raw request body before JSON parsing. 2. Read the X-Signature header from the request. 3. Verify the webhook with HMAC-SHA256 using the raw request body and the merchant API key from server secrets. 4. Use a constant-time comparison for signatures. 5. Reject missing or invalid signatures before parsing or trusting the payload. 6. Parse JSON only after signature verification passes. 7. Handle invoice.paid, invoice.expired, and aml.rejected.invoice events. 8. Persist terminal status changes in my database. invoice.paid is terminal success. invoice.expired and aml.rejected.invoice are terminal failure states. 9. Treat partial as not fully paid. Persist the partial state or payment progress without releasing goods or marking the order paid. 10. Make webhook handling idempotent so duplicate deliveries don't double-apply business effects. Use X-Idempotency-Key when present; it is currently guaranteed only for withdrawal events, so use an event/entity deduplication key otherwise. 11. Return a 2xx response only after the event is accepted for processing. Return a clear non-2xx response for invalid signatures or malformed payloads. 12. Add user-project tests and checks for raw body preservation, X-Signature verification, invalid signature rejection, duplicate delivery handling, invoice.paid, invoice.expired, aml.rejected.invoice, partial, and expired status persistence. 13. Run the relevant lint, type, unit, integration, and build checks available in my project, then report the exact commands and results. Do not verify after parsing JSON. The raw request body must be captured and verified first. ``` ## Production hardening review prompt ```markdown You are reviewing my CoinsSend checkout integration for production readiness. Use the canonical CoinsSend getting started, quickstart, authentication, invoices, webhooks, error handling, and rate limit docs as the source of truth. Inspect my existing implementation, tests, configuration, and runtime assumptions. Review checklist: 1. Confirm merchant API keys are stored only in environment variables or server secrets and never exposed to frontend code, logs, build output, public repositories, or browser storage. 2. Confirm invoice creation happens server-side and merchant API requests are signed only on the server. 3. Confirm request signing generates the PHP-compatible canonical JSON body, signs md5(base64(canonical_body) + API_KEY) for legacy endpoints or timestamped HMAC-SHA256 for withdrawals, sends the same canonical body, and passes signature-test-vectors.json. 4. Confirm success and cancel redirect URLs are treated only as UX signals, never as payment confirmation. 5. Confirm verified webhooks or persisted status checks are the only source of payment confirmation. 6. Confirm webhook verification reads X-Signature, uses HMAC-SHA256, and verifies the raw request body before JSON parsing. 7. Confirm terminal state persistence covers invoice.paid as success and invoice.expired plus aml.rejected.invoice as failure states. 8. Confirm partial and expired statuses don't release goods or mark orders paid. 9. Confirm duplicate webhook deliveries are idempotent and repeated checkout starts use a local uniqueness guard without assuming the CoinsSend API replays a duplicate invoice response. 10. Confirm failures are handled clearly, including authentication and endpoint error shapes, invalid signatures, validation errors, rate limits, unknown write outcomes, expired invoices, partial payments, and AML rejection. 11. Confirm observability doesn't leak merchant secrets or signed payload material. 12. Confirm my user-project tests and checks cover signing, invoice creation, redirect behavior, webhook verification, terminal persistence, failure states, partial, expired, and secret handling. 13. Run the relevant lint, type, unit, integration, and build checks available in my project, then report the exact commands and results. Do not add new SDK-specific setup instructions or copy full endpoint reference sections. If docs are needed, link back to the canonical CoinsSend docs. ``` ## Required guardrails - Merchant API keys stay in environment variables or server secrets only. - Frontend code must never sign merchant API requests or store merchant API keys. - Invoice creation for checkout must happen on the server. - Invoice and static-wallet request signing must use exactly `md5(base64(canonical_body) + API_KEY)`. - Withdrawal request signing must send `Timestamp` and use exactly `hmac_sha256(base64(canonical_body) + "." + Timestamp, API_KEY)`. - Generate one PHP-compatible canonical request body, sign it, send that same string, and pass the published signature vectors. - Webhook verification is a separate contract: verify the exact raw received body bytes before JSON parsing. - Webhook receivers must read `X-Signature`, verify with HMAC-SHA256, and preserve the raw request body before JSON parsing. - Redirect success and cancel URLs are UX signals only, not payment confirmation. - Payment confirmation must come from verified webhooks and terminal status persistence. - Handle `invoice.paid`, `invoice.expired`, `aml.rejected.invoice`, `partial`, and `expired` explicitly. - Persist terminal outcomes so `invoice.paid` closes the order as paid, while `invoice.expired` and `aml.rejected.invoice` close it as failed or rejected. - Treat `partial` as not fully paid unless a separate business rule is intentionally implemented. - Add user-project tests and checks for signing, webhook verification, redirects, persistence, failure states, duplicate deliveries, and secret handling. - Link back to the canonical docs pages instead of duplicating endpoint references. ## What not to ask the assistant to do - Don't ask it to put merchant API keys in frontend code, mobile clients, public repositories, logs, browser storage, or build output. - Don't ask it to sign CoinsSend merchant requests in the browser. - Don't ask it to mark an order paid after a success redirect. - Don't ask it to trust a cancel redirect as the final invoice state. - Don't ask it to verify webhooks after parsing JSON. Preserve and verify the raw request body first. - Don't ask it to skip `X-Signature` validation or replace HMAC-SHA256 with another webhook signing method. - Don't ask it to ignore `partial`, `expired`, `invoice.expired`, or `aml.rejected.invoice` states. - Don't ask it to blindly retry a withdrawal, static-wallet creation, or another write after an unknown outcome. - Don't ask it to invent a universal `Idempotency-Key`, global rate limit, symbolic wire error code, or static asset enum. - Don't ask it to hardcode credentials, merchant IDs, signatures, or production-looking secrets. - Don't ask it to duplicate whole endpoint reference sections when a link to the canonical docs is enough. --- Source: https://docs.coinssend.com/authentication.md # Authentication CoinsSend merchant API endpoints use request signatures. Keep signing code and the API key on a trusted server; never put a merchant API key in browser or mobile code. Authenticated endpoints require: | Header | Required | Description | | ----------- | ---------------------------------------------------- | ----------------------------------------------------------------- | | `Merchant` | Yes | Merchant ID | | `Sign` | Yes | Lowercase hexadecimal request signature | | `Timestamp` | Withdrawals: yes; other merchant endpoints: optional | Unix timestamp in seconds. `X-Timestamp` is accepted as an alias. | Public catalog and checkout lookup endpoints do not use these headers. See the verified allowlist in [API Reference](https://docs.coinssend.com/api-reference.md) or [OpenAPI](https://docs.coinssend.com/openapi.json). ## Canonical JSON Body Request signing does **not** use arbitrary raw JSON formatting. For a JSON object or array, the API parses the body and re-encodes it with PHP's default `json_encode` behavior before verifying the signature. That canonical form: - has no insignificant whitespace; - preserves object member order; - escapes `/` as `\/`; - escapes non-ASCII characters as lowercase `\uXXXX` sequences; - uses an empty string for a request with no body. Generate this canonical JSON once, sign it, and send those same bytes as the HTTP body. Keep money values as strings and avoid numeric object keys or language-specific floating-point values. Interoperability fixtures, including whitespace, URL, Unicode, withdrawal, and empty-GET cases, are in [`signature-test-vectors.json`](https://docs.coinssend.com/signature-test-vectors.json). ### Canonical JSON Helpers
```php `\\u${character.charCodeAt(0).toString(16).padStart(4, '0')}` ) } ``` ```python import json def canonical_json(payload): encoded = json.dumps( payload, ensure_ascii=False, separators=(',', ':'), allow_nan=False, ) escaped = [] for character in encoded: codepoint = ord(character) if character == '/': escaped.append(r'\/') elif codepoint < 0x80: escaped.append(character) elif codepoint <= 0xFFFF: escaped.append(f'\\u{codepoint:04x}') else: codepoint -= 0x10000 escaped.append(f'\\u{0xD800 + (codepoint >> 10):04x}') escaped.append(f'\\u{0xDC00 + (codepoint & 0x3FF):04x}') return ''.join(escaped) ```
## Legacy MD5 Signature Invoice creation, wallet creation, balances, and fees accept the legacy signature when no timestamp header is supplied: ```text canonical_body = canonical_json(request_object) or "" for a bodyless request payload_base64 = base64_utf8(canonical_body) Sign = md5(payload_base64 + API_KEY) ``` MD5 here is part of the current compatibility protocol, not a password-hashing recommendation. Always use HTTPS and keep the key server-side.
```php ## Timestamped HMAC Signature `POST /v1/withdrawals` requires a Unix timestamp within 300 seconds of server time. Other authenticated endpoints also accept this mode when `Timestamp` or `X-Timestamp` is supplied. ```text canonical_body = canonical_json(request_object) or "" for a bodyless request payload_base64 = base64_utf8(canonical_body) message = payload_base64 + "." + Timestamp Sign = hmac_sha256(message, API_KEY) ``` Send exactly the timestamp string used in the signature: ```text Merchant: 12345678-1234-1234-1234-123456789012 Timestamp: 1716200000 Sign: 64-character-lowercase-hmac-sha256-hex ```
```php The timestamp prevents stale replay but does not make a write idempotent. In particular, it is unsafe to automatically repeat a withdrawal after an unknown outcome. ## Complete Invoice Example ```javascript const crypto = require('node:crypto') function canonicalJson(payload) { return JSON.stringify(payload) .replace(/\//g, '\\/') .replace(/[\u0080-\uFFFF]/g, character => `\\u${character.charCodeAt(0).toString(16).padStart(4, '0')}` ) } const payload = { order_id: 'order_12345', amount: '100.50', success_url: 'https://merchant.example/success' } const body = canonicalJson(payload) const payloadBase64 = Buffer.from(body, 'utf8').toString('base64') const sign = crypto.createHash('md5').update(payloadBase64 + process.env.COINSSEND_API_KEY).digest('hex') const response = await fetch('https://api.coinssend.com/v1/invoices', { method: 'POST', headers: { 'Content-Type': 'application/json', Merchant: process.env.COINSSEND_MERCHANT_ID, Sign: sign }, body }) ``` ## GET Requests For an authenticated GET with no body, the canonical body and Base64 payload are both empty. The legacy signature is therefore: ```text Sign = md5("" + API_KEY) ``` Do not add merchant signature headers to public `GET /v1/invoices/{invoiceCode}`, `GET /v1/get-coin-rate`, or `GET /v1/coins-and-fee` calls. ## Authentication Errors Authentication failures return a compact body such as: ```json { "error": "Invalid sign" } ``` | HTTP status | Message | Meaning | | ----------- | ------------------------------- | ----------------------------------------------------- | | `400` | `Missing headers` | `Merchant` or `Sign` is missing | | `400` | `Missing timestamp` | Withdrawal timestamp is missing | | `401` | `Invalid merchant` | Merchant or API key was not found | | `401` | `Invalid sign` | Signature verification failed | | `401` | `Timestamp expired` | Timestamp is invalid or outside the 300-second window | | `403` | `Merchant account is suspended` | Merchant is suspended | These messages are not a separate `code` property. Endpoint and validation errors use different documented shapes; always branch on the HTTP status and then parse the applicable schema. ## Operational Guidance - Keep the API key in a secrets manager or server environment variable. - Generate and send the body as one immutable string. - Use a clock synchronized with NTP for withdrawal timestamps. - Compare webhook HMAC values in constant time; webhook signing is a separate raw-body contract described in [Webhooks](https://docs.coinssend.com/webhooks.md). - Use the published test vectors in automated client tests before making live calls. --- Source: https://docs.coinssend.com/api-reference.md # API Reference This page lists the supported public integration operations. The machine-readable source is [OpenAPI 3.1](https://docs.coinssend.com/openapi.json). ## Base URL ``` https://api.coinssend.com/v1/ ``` ## Authentication For detailed authentication information, see [Authentication](https://docs.coinssend.com/authentication.md). ## Headers | Header | Description | | -------------- | -------------------------------------------------------------------------------------------------------------------------------------- | | `Content-Type` | Must be `application/json` for all requests | | `Merchant` | Your merchant ID (required for authenticated endpoints) | | `Sign` | Request signature (required for authenticated endpoints) | | `Timestamp` | Unix timestamp in seconds. Required for `POST /v1/withdrawals`; optional only when using timestamped HMAC on other merchant endpoints. | ## Endpoints Summary | Method | Path | Description | Authentication | | ------ | ------------------------------------------------- | ---------------------------------------------------------------- | --------------------------- | | `POST` | `/v1/invoices` | Create a new invoice | Merchant + Sign | | `POST` | `/v1/withdrawals` | Initiate a withdrawal | Merchant + Timestamp + Sign | | `GET` | `/v1/merchants/balances` | Get merchant balances | Merchant + Sign | | `GET` | `/v1/merchants/fees` | Get merchant fees | Merchant + Sign | | `POST` | `/v1/wallet-address` | Create static wallet address | Merchant + Sign | | `GET` | `/v1/get-coin-rate` | Get current USD exchange rates for supported assets | None | | `GET` | `/v1/coins-and-fee` | Get the current supported coin/network catalog with fee metadata | None | | `GET` | `/v1/invoices/{invoiceCode}` | Get public invoice checkout state | None | | `POST` | `/v1/invoices/{invoiceCode}/provider-card-orders` | Start or reuse active card checkout | None | | `GET` | `/v1/wallet-addresses/{walletId}/qr` | Render the QR URL returned by wallet/invoice responses | None | ## Detailed API Documentation ### Invoices - [Create Invoice](https://docs.coinssend.com/invoices.md#create-invoice) - [Get Invoice](https://docs.coinssend.com/invoices.md#retrieve-invoice) - [Create Provider-Card Order](https://docs.coinssend.com/invoices.md#create-provider-card-order) ### Withdrawals - [Initiate Withdrawal](https://docs.coinssend.com/withdrawals.md#initiate-withdrawal) ### Merchants - [Get Merchant Balances](https://docs.coinssend.com/merchants.md#get-merchant-balances) - [Get Merchant Fees](https://docs.coinssend.com/merchants.md#get-merchant-fees) ### Static Wallets - [Create Wallet Address](https://docs.coinssend.com/static-wallets.md#create-wallet-address) ### Supported Coins, Networks & Rates {#coin-rates} - [Supported Coins & Networks](https://docs.coinssend.com/supported-coins.md) - [Get Coin Rates](https://docs.coinssend.com/coin-rates.md#get-coin-rates) - [Get Supported Coins & Fee](https://docs.coinssend.com/coin-rates.md#get-supported-coins-and-fee) ## Supported Coins & Networks Browse the [coin and network tables](https://docs.coinssend.com/supported-coins.md) for display names, API identifiers, and guidance on building payment selectors. Use `GET /v1/coins-and-fee` for live discovery instead of copying a static coin or network list into an integration. Availability, limits, fees, and precision can change without an API schema change. Individual write endpoints remain the final authority for accepting a pair; the catalog is not an offline validation guarantee. See [Coin Rates](https://docs.coinssend.com/coin-rates.md). ### Webhooks - [Webhook Events](https://docs.coinssend.com/webhooks.md#webhook-events) - [Webhook Authentication](https://docs.coinssend.com/webhooks.md#authentication) ## Common Response Formats Most operations use the following success envelope: ### Success Response ```json { "status": "success", "data": { // Response data specific to the endpoint } } ``` ### Endpoint Error Response ```json { "status": "error", "message": "Error description" } ``` Some errors include additional fields (for example, fee breakdowns) alongside `status` and `message`. Authentication errors may instead return `{"error": "Reason"}` without the `status` field. Always rely on the HTTP status code first. Validation failures use HTTP `422` with `errors`, `status`, and `message`. Symbolic labels shown in prose are not guaranteed response fields. ## Response Data Types The following data types are used throughout API responses: | Type | Description | Example | | --------- | -------------------------------------- | -------------------- | | `string` | Text values | `"example"` | | `integer` | Whole numbers | `42` | | `float` | Numbers with decimals | `10.5` | | `boolean` | `true` or `false` values | `true` | | `array` | Ordered list of values | `[1, 2, 3]` | | `object` | JSON object containing key/value pairs | `{ "key": "value" }` | ### Amount Precision All monetary amounts, percentage values, and currency rates in API responses are returned as **strings**. Using strings preserves accuracy for values with many decimals. Request parameters should also be sent as strings containing numeric values. ## Common HTTP Error Responses | HTTP Code | Description | | --------- | ----------------------------------------------------------------------------- | | 400 | Bad Request - The request was invalid or cannot be served | | 401 | Unauthorized - Authentication credentials are missing or invalid | | 403 | Forbidden - The request is understood, but it has been refused | | 404 | Not Found - The requested resource does not exist | | 422 | Unprocessable Entity - The request was well-formed but could not be processed | | 429 | Too Many Requests - Rate limit exceeded | | 500 | Server Error - Something went wrong on our end | ## Rate Limits The only merchant-public write limit verified in this application is the wallet-creation limit: 100 successful creations per merchant in a 60-second window. Additional limits may apply. See [Rate Limits](https://docs.coinssend.com/rate-limits.md). ## Versioning The API is versioned in the URL path (`/v1/`). When breaking changes are introduced, a new version number will be used. --- Source: https://docs.coinssend.com/invoices.md # Invoices API

The Invoices API allows you to create and manage cryptocurrency payment invoices for your customers. Each invoice generates a unique payment page where customers can select their preferred cryptocurrency and complete the payment. Invoices are ideal for e-commerce checkout flows and single-payment scenarios.

## Overview An invoice is a payment request with a specific amount that customers can pay using various cryptocurrencies. When an invoice is created, customers receive a payment URL where they can choose their preferred cryptocurrency and complete the payment. ### Invoice Lifecycle
1

Create Invoice

Generate a new invoice with a specified amount using the POST /v1/invoices endpoint.

2

Customer Payment

Customer visits the payment URL, selects direct crypto payment, or uses card checkout when the invoice allows cards and the remaining amount meets the minimum.

3

Payment Confirmation

Once payment is confirmed on the blockchain, the invoice status is updated to "paid". Partial payments are supported - if the customer pays less than the full amount, they can complete the payment with a second transaction for the remaining balance.

4

Webhook Notification

A webhook notification is sent to your server with payment details.

## API Endpoints ### Create Invoice Creates a new invoice for direct cryptocurrency payment, with optional card checkout.
POST /v1/invoices

This endpoint creates a new invoice with the specified amount and options. The invoice will generate a unique payment page URL that you can redirect your customers to for completing the payment.

Authentication required
#### Request Headers | Header | Required | Description | | -------------- | -------- | -------------------------------------------------------------------------------------- | | `Content-Type` | Yes | Must be `application/json` | | `Merchant` | Yes | Your merchant ID | | `Sign` | Yes | Request signature (see [Authentication](https://docs.coinssend.com/authentication.md)) | | `Timestamp` | No | Include only when using timestamped HMAC; `X-Timestamp` is accepted as an alias | Build the canonical JSON body described in [Authentication](https://docs.coinssend.com/authentication.md), sign it, and send that same string as the request body. #### Request Parameters | Parameter | Type | Required | Default | Description | | ------------------------- | ------- | -------- | ------------------- | ------------------------------------------------------------------------------------------------------------------- | | `order_id` | string | Yes | - | Your order identifier (must be unique per merchant) | | `amount` | string | Yes | - | Invoice amount in USD (minimum `3.00`, e.g., "10.50") | | `is_customer_fee` | boolean | No | false | If true, the customer pays the service fee (CoinsSend commission) | | `is_customer_network_fee` | boolean | No | false | If true, the customer pays the blockchain transaction fee; if false, the merchant covers it | | `allow_card_payments` | boolean | No | false | If true, card checkout may be shown when the invoice amount or remaining amount is at least `$15.00` / `1500` cents | | `allowed_coins` | array | No | all supported coins | List of currently supported cryptocurrencies to allow for this invoice | | `coin` | string | No | - | Preselect a currently supported coin for the payment page | | `network` | string | No | - | Preselect a network that matches the selected coin | | `success_url` | string | No | - | URL to redirect the customer after successful payment | | `cancel_url` | string | No | - | URL to redirect the customer if they cancel payment | > **Important:** `coin` and `network` must be a currently supported combination. > Fetch the live catalog from `GET /v1/coins-and-fee`. Requests containing an > unsupported pair (for example `eth` on `tron`) or unsupported > `allowed_coins` entries fail validation with HTTP `422`. See [Supported Coins & Networks](https://docs.coinssend.com/supported-coins.md) for the coin/network tables and guidance on selecting invoice payment options. > **Card checkout:** `allow_card_payments` defaults to `false`. Card checkout is not shown or available unless you set `allow_card_payments` to `true` for that invoice. Even when cards are allowed, the invoice amount or remaining amount must be at least `$15.00` / `1500` cents. Direct crypto payment remains available when cards are disabled or the remaining amount is below the card minimum. #### Request Example ```json { "order_id": "order_12345", "amount": "100.50", "is_customer_fee": true, "is_customer_network_fee": false, "allow_card_payments": true, "allowed_coins": ["usdt", "eth"], "coin": "usdt", "network": "tron", "success_url": "https://example.com/payment-success", "cancel_url": "https://example.com/payment-canceled" } ``` #### Success Response ```json { "status": "success", "data": { "id": "550e8400-e29b-41d4-a716-446655440000", "order_id": "order_12345", "url": "https://pay.coinssend.com/invoice_code", "code": "invoice_code", "amount": "100.50", "payer_amount": "100.50", "expired_at": "2024-01-02T12:00:00+00:00", "created_at": "2024-01-01T12:00:00+00:00" } } ``` #### Response Field Descriptions | Field | Type | Description | | -------------- | ------ | --------------------------------------------------------------------- | | `id` | string | Unique invoice ID (UUID) | | `order_id` | string | Unique order ID in your system | | `url` | string | Payment URL for the invoice | | `code` | string | Unique invoice code | | `amount` | string | Invoice amount | | `payer_amount` | string | Total amount the payer needs to pay including fees | | `expired_at` | string | ISO 8601 timestamp when the invoice expires (24 hours after creation) | | `created_at` | string | ISO 8601 timestamp when the invoice was created | `allow_card_payments` and `provider_card` are not returned by `POST /v1/invoices`. They can appear in invoice lookup and payment-page invoice payloads, where `provider_card` explains whether card checkout is currently available and why it may be disabled. #### Card Payment Availability Direct crypto payment is always available on the payment page. Card checkout is optional per invoice and requires both of these conditions: 1. `allow_card_payments` is `true` on the invoice. 2. The invoice amount, or remaining amount after a partial payment, is at least `$15.00` / `1500` cents. If either condition is not met, the payment page does not show card checkout and customers can still pay directly with crypto. In invoice lookup and payment-page invoice payloads, the `provider_card` object explains the current card state with `enabled`, `disabled_reason`, `minimum_amount_cents`, and `remaining_amount_cents`. Provider redirects, KYC success, provider webhooks, and provider status polling are not final payment confirmation. Chain confirmation remains the source of truth for moving an invoice to `paid`. ### Create Provider-Card Order Creates a card checkout order for an existing invoice when `allow_card_payments` is enabled and the remaining invoice amount is at least `$15.00` / `1500` cents.
POST /v1/invoices/{invoiceCode}/provider-card-orders
#### Request Parameters | Parameter | Type | Required | Default | Description | | --------- | ------ | -------- | ------- | ------------------------------------------------ | | `email` | string | No | - | Optional customer email to store on the invoice. | Do not send `provider`, `coin`, or `network` fields for card checkout. CoinsSend selects these values. If a client includes them, they are ignored and cannot override the selected checkout route. #### Request Example ```json { "email": "customer@example.com" } ``` #### Success Response ```json { "status": "success", "data": { "provider_card_order": { "id": "550e8400-e29b-41d4-a716-446655440000", "provider": "simpleswap", "local_status": "redirect_pending", "expires_at": "2026-05-11T12:30:00+00:00", "redirect_url": "https://provider.example/checkout/session" }, "reused": false } } ``` Repeating this operation while the same active order is usable returns that order with `reused: true`. This narrow active-order reuse is not a general `Idempotency-Key` contract. ### Retrieve Invoice {#retrieve-invoice} Returns the current public checkout state for an invoice. ```text GET /v1/invoices/{invoiceCode} ``` No merchant signature is required. The response uses the standard success envelope with `data.invoice` and current `data.network_fees`. The invoice object includes amounts, payment selection, status, allowed payment options, transaction hashes, QR URL, and `provider_card` capability. See the exact machine-readable schema in [OpenAPI](https://docs.coinssend.com/openapi.json). Treat the invoice code as a public checkout identifier. Do not use it as authorization for merchant balances, fees, wallet creation, or withdrawals. > **AI assistant prompt:** Build a server-side invoice creation and buyer redirect flow. > Create `POST /v1/invoices`, store the invoice ID, code, status, and URL, then send the buyer to `data.url`. > Treat success/cancel redirect URLs as UX signals only, not payment confirmation. > Confirm payment through [Webhooks](https://docs.coinssend.com/webhooks.md), handle `partial` as unpaid progress, and handle `expired` as a terminal failure state. > For the full checkout prompt, see [AI Integration Prompts](https://docs.coinssend.com/ai-integration-prompts.md). #### Error Responses | HTTP status | Response shape | Description | | ----------- | ------------------------------------------------------------ | --------------------------------------------------------------------------- | | `400` | `{"error":"Missing headers"}` | Merchant or Sign header is missing | | `401` | `{"error":"Invalid merchant"}` or `{"error":"Invalid sign"}` | Authentication failed | | `403` | authentication or endpoint error | Merchant status blocks invoice creation | | `409` | `{"status":"error","message":"Order id already exists"}` | The merchant order ID already exists; the original response is not replayed | | `422` | validation error with `errors`, `status`, and `message` | Request parameters are invalid | ### Invoice Fees When an invoice is paid, the amount your customer must send is shown in the `payer_amount` field. This value includes the blockchain network fee and may also include the service fee depending on the `is_customer_fee` setting. 1. **Service Fee (CoinsSend commission)** – Calculated as `amount × service_fee_percent`. It is added to `payer_amount` only when `is_customer_fee` is `true`. Otherwise this fee is deducted from the merchant balance. 2. **Network/Transaction Fee** – Specified in the same coin as the invoice payment. Network fee values can be obtained from the `network_fees` list in the [Merchant API](https://docs.coinssend.com/merchants.md). It is added to `payer_amount` only when `is_customer_network_fee` is `true`. When the merchant covers it (`false`), the customer sees no network surcharge and the fee is deducted from the merchant proceeds. The final crypto amount to pay is calculated as: ```text amount_coin = (amount + (service_fee if is_customer_fee else 0)) / rate total_coin = amount_coin + (network_fee if is_customer_network_fee else 0) ``` #### Fee Calculation Examples **Customer Pays Service Fee** ``` Invoice amount: 100 USD Network fee: 1 USD Service fee percent: 0.45% Service fee: 100 × 0.45% = 0.45 USD Total amount to pay: 100 + 1 + 0.45 = 101.45 USD ``` **Merchant Pays Service Fee** ``` Invoice amount: 100 USD Network fee: 1 USD Service fee percent: 0.45% Service fee: 100 × 0.45% = 0.45 USD Total amount to pay: 100 + 1 = 101 USD Service fee deducted from merchant balance: 0.45 USD ``` **Merchant Pays Network Fee** ``` Invoice amount: 100 USD Network fee: 1 USD Service fee percent: 0.45% Network fee covered by merchant ➜ payer sends: 100.45 USD if customer pays service fee or 100 USD if merchant also covers service fee. ``` **ETH USDC Example** Using the ETH network with USDC, the exchange rate on 2025‑07‑09 is `1 USDC = 1.0000363274333 USD`. ``` Invoice amount: 100 USD Network fee: 1 USDC Service fee percent: 0.45% Service fee: 100 × 0.45% = 0.45 USD Amount before network fee: 100 + 0.45 = 100.45 USD Amount in USDC: 100.45 ÷ 1.000036327 ≈ 100.4463293 USDC Total to send: 100.4463293 + 1 ≈ 101.4463293 USDC ``` The service fee is rounded down to 8 decimal places when converting to cryptocurrency because invoice amounts are stored in cents. The formula is: ```text fee_coin = floor(amount_coin × service_fee_percent × 100000000) / 100000000 ``` Where `amount_coin` is the invoice amount in the selected coin before the network fee is added. #### Code Examples
```php 'order_' . time(), 'amount' => '100.50', 'is_customer_fee' => true, 'allow_card_payments' => true, 'success_url' => 'https://example.com/payment-success', 'cancel_url' => 'https://example.com/payment-canceled' ]; // Generate signature $jsonData = json_encode($invoiceData); $base64Data = base64_encode($jsonData); $signature = md5($base64Data . $apiKey); // Set up request $ch = curl_init('https://api.coinssend.com/v1/invoices'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonData); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json', 'Merchant: ' . $merchantId, 'Sign: ' . $signature ]); // Execute request $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); // Process response if ($httpCode >= 200 && $httpCode < 300) { $responseData = json_decode($response, true); if ($responseData['status'] === 'success') { echo "Invoice created successfully!\n"; echo "Payment URL: " . $responseData['data']['url'] . "\n"; echo "Invoice Code: " . $responseData['data']['code'] . "\n"; } } else { echo "Error: " . $response . "\n"; } ``` ```javascript // API credentials const merchantId = 'your_merchant_id'; const apiKey = 'your_api_key'; // Invoice data const invoiceData = { order_id: 'order_' + Date.now(), amount: '100.50', is_customer_fee: true, allow_card_payments: true, success_url: 'https://example.com/payment-success', cancel_url: 'https://example.com/payment-canceled' }; // Generate signature const jsonData = JSON.stringify(invoiceData) .replace(/\//g, '\\/') .replace(/[\u0080-\uFFFF]/g, character => `\\u${character.charCodeAt(0).toString(16).padStart(4, '0')}` ); const base64Data = Buffer.from(jsonData, 'utf8').toString('base64'); const crypto = require('node:crypto'); const signature = crypto.createHash('md5').update(base64Data + apiKey).digest('hex'); // Make the API request fetch('https://api.coinssend.com/v1/invoices', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Merchant': merchantId, 'Sign': signature }, body: jsonData }) .then(response => response.json()) .then(data => { if (data.status === 'success') { console.log("Invoice created successfully!"); console.log("Payment URL:", data.data.url); console.log("Invoice Code:", data.data.code); } else { console.error("Error:", data); } }) .catch(error => console.error("Request failed:", error)); ``` ```python import requests import json import base64 import hashlib import time # API credentials merchant_id = 'your_merchant_id' api_key = 'your_api_key' # Invoice data invoice_data = { 'order_id': f'order_{int(time.time())}', 'amount': '100.50', 'is_customer_fee': True, 'allow_card_payments': True, 'success_url': 'https://example.com/payment-success', 'cancel_url': 'https://example.com/payment-canceled' } def canonical_json(payload): encoded = json.dumps( payload, ensure_ascii=False, separators=(',', ':'), allow_nan=False ) escaped = [] for character in encoded: codepoint = ord(character) if character == '/': escaped.append(r'\/') elif codepoint < 0x80: escaped.append(character) elif codepoint <= 0xFFFF: escaped.append(f'\\u{codepoint:04x}') else: codepoint -= 0x10000 escaped.append(f'\\u{0xD800 + (codepoint >> 10):04x}') escaped.append(f'\\u{0xDC00 + (codepoint & 0x3FF):04x}') return ''.join(escaped) # Generate signature from the exact body that will be sent json_data = canonical_json(invoice_data) base64_data = base64.b64encode(json_data.encode()).decode() signature = hashlib.md5((base64_data + api_key).encode()).hexdigest() # Set up headers headers = { 'Content-Type': 'application/json', 'Merchant': merchant_id, 'Sign': signature } # Make the API request response = requests.post( 'https://api.coinssend.com/v1/invoices', headers=headers, data=json_data ) # Process response if response.status_code >= 200 and response.status_code < 300: data = response.json() if data['status'] == 'success': print("Invoice created successfully!") print(f"Payment URL: {data['data']['url']}") print(f"Invoice Code: {data['data']['code']}") else: print(f"Error: {data}") else: print(f"HTTP Error: {response.status_code}") print(f"Response: {response.text}") ```
## Invoice Statuses Invoices transition through the following states: | Status | Description | | -------------- | -------------------------------------------------------------- | | `new` | Invoice created; no payment method selected. | | `waiting` | Payment method locked in; awaiting blockchain payment. | | `partial` | Partial payment detected; remaining balance still due. | | `paid` | Full payment confirmed. | | `expired` | Invoice expired (24 hours after creation) before full payment. | | `aml_rejected` | Payment was rejected by AML processing. |
Note: Invoice webhook events are currently emitted for invoice.paid, invoice.expired, and AML rejection. Do not assume every intermediate status has an event. See the Webhooks documentation for details.
## Best Practices - **Store Invoice Data**: Always store the invoice code and status in your database for tracking and reconciliation. - **Handle Webhooks**: Implement webhook handling to receive real-time notifications of payment status changes. - **Support Partial Payments**: Implement UI and business logic to handle partially paid invoices, allowing customers to complete payments in multiple transactions. - **Track Payment Progress**: Use `payed_usd`, `payed_crypto`, and payer amount fields from invoice lookup to show payment progress. - **Use Card Checkout Deliberately**: Set `allow_card_payments` only when you want card checkout available. Keep direct crypto payment available for invoices that do not allow cards or fall below the `$15.00` card minimum after partial payment. - **Verify Webhooks**: Always verify webhook signatures to ensure they come from CoinsSend. - **Check Expiration**: Invoices expire after 24 hours. Consider this when implementing payment flows. ## Related Resources - [Webhooks](https://docs.coinssend.com/webhooks.md) - Receive real-time notifications for invoice status changes - [Authentication](https://docs.coinssend.com/authentication.md) - Learn about API authentication and signatures - [Error Handling](https://docs.coinssend.com/error-handling.md) - Understand error responses and how to handle them ## Code Examples For complete code examples in various languages, see: - [PHP Examples](https://docs.coinssend.com/examples/php-invoice.md) - [JavaScript Examples](https://docs.coinssend.com/examples/js-invoice.md) - [Python Examples](https://docs.coinssend.com/examples/python-invoice.md) --- Source: https://docs.coinssend.com/static-wallets.md # Static Wallets API Static wallets are reusable cryptocurrency addresses. Create them with the merchant-signed API, then process deposits through signed webhooks. ## Create a wallet address {#create-wallet-address} ```http POST /v1/wallet-address ``` ### Headers | Name | Required | Description | | -------------- | -------: | -------------------------------------------------------------------- | | `Content-Type` | Yes | `application/json` | | `Merchant` | Yes | Merchant ID | | `Sign` | Yes | Signature of the canonical request body | | `Timestamp` | No | Unix seconds. If present, `Sign` must use the timestamped HMAC form. | See [Authentication](https://docs.coinssend.com/authentication.md) and the executable [signature test vectors](https://docs.coinssend.com/signature-test-vectors.json). The signature is made from PHP-compatible canonical JSON, and those exact canonical bytes must be sent as the HTTP body. Do not sign plain `JSON.stringify()` or default `json.dumps()` output. ### Request body | Field | Type | Required | Description | | ------------- | -------------- | -------: | ------------------------------------------------------ | | `network` | string | Yes | Network identifier. Input is trimmed and lowercased. | | `coin` | string | Yes | Coin identifier. Input is trimmed and lowercased. | | `type` | string | No | `static` (default) or `merchant`. | | `label` | string or null | No | Merchant-defined label, at most 255 characters. | | `webhook_url` | URI or null | No | Address-specific callback URL, at most 255 characters. | ```json { "network": "tron", "coin": "usdt", "type": "static", "label": "Checkout deposits", "webhook_url": "https:\/\/merchant.example\/coinssend\/webhook" } ``` The network/coin example is illustrative. Asset availability can change. Use `GET /v1/coins-and-fee` for discovery, but treat the create request's validation response as final because wallet creation may not be available for every listed pair at every moment. See [Supported Coins & Networks](https://docs.coinssend.com/supported-coins.md) for the coin/network tables, API identifiers, and wallet selection guidance. Multiple `static` wallets can be created for the same pair. A merchant can have only one `merchant` wallet for a pair. A `merchant` wallet deposit is credited without the static-wallet fee or 24-hour hold; those are different product semantics, not an idempotency guarantee. ### Success response ```json { "status": "success", "data": { "address": "TXHXiQ2aXYqtz3y9gEGiVjzxXxXechdLwE", "qr_code_url": "https://api.coinssend.com/v1/wallet-addresses/87654321-4321-4321-4321-210987654321/qr" } } ``` `qr_code_url` is a public binary-image endpoint. Its path parameter is the wallet-address record ID, not the blockchain address. Complete server-side examples: - [PHP](https://docs.coinssend.com/examples/php-static-wallet.md) - [Node.js](https://docs.coinssend.com/examples/js-static-wallet.md) - [Python](https://docs.coinssend.com/examples/python-static-wallet.md) Keep the API key on a trusted server. Browser-side signing exposes the key. ## Errors There is no symbolic `code` field in the current wire contract. Authentication errors return a simple `error` string, endpoint errors return `status`/`message`, and validation failures return `message` plus `errors`. | HTTP status | Current condition | Body shape | | ----------: | -------------------------------------------------------- | --------------------------------------------------------------------------------------- | | `400` | Missing `Merchant` or `Sign` | `{"error":"Missing headers"}` | | `400` | Duplicate `merchant` wallet or another request error | `{"status":"error","message":"..."}` | | `401` | Invalid merchant, signature, or timestamp | `{"error":"..."}` | | `403` | Merchant status blocks wallet creation | `{"status":"error","message":"..."}` or authentication `error` shape | | `409` | Concurrent creation already in progress for the merchant | `{"status":"error","message":"Wallet creation already in progress for this merchant."}` | | `422` | Invalid fields or unsupported pair | `{"message":"...","errors":{"field":["..."]}}` | | `429` | Wallet-creation limit reached | `{"status":"error","message":"Too many requests. Please try again later."}` | The application records at most 100 successful wallet creations per merchant in a 60-second window. Infrastructure in front of the application may enforce additional limits, so clients must also honor `429` and `Retry-After` when it is present. Wallet creation has no request idempotency-key contract. A timed-out `POST` may have succeeded, and retrying a `static` request can create another address. Do not retry automatically; reconcile the outcome through merchant tooling or support before issuing another creation request. ## Current fees and balances Do not hard-code fee values. Fetch merchant-specific percentages and configured network fees from signed `GET /v1/merchants/fees`, and use `GET /v1/coins-and-fee` for the current public asset/fee catalog. Values and enabled pairs can change. For `static` wallets, CoinsSend deducts the configured merchant and network fees before crediting the net amount. A static-wallet deposit is held for 24 hours before becoming available. For `merchant` wallets, those fees are not deducted and the deposit is immediately available. Signed webhook amounts are the authoritative transaction snapshot for your reconciliation record. ## Deposit webhook The public payload event for a successful wallet deposit is `wallet.transaction`. Use only the event values documented here and in [Webhooks](https://docs.coinssend.com/webhooks.md). ```json { "event": "wallet.transaction", "timestamp": 1746984120, "data": { "wallet": { "address": "TXHXiQ2aXYqtz3y9gEGiVjzxXxXechdLwE", "network": "tron", "coin": "usdt", "label": "Checkout deposits", "rate": "1.0001" }, "transaction": { "id": "87654321-4321-4321-4321-210987654321", "hash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", "block_number": 53214321, "amounts": { "gross": {"amount": "10.05", "currency": "usd", "crypto": "10.05"}, "net": {"amount": "9.849", "currency": "usd", "crypto": "9.849"} }, "fees": { "merchant": {"amount": "0.201", "currency": "usd", "crypto": "0.201"}, "network": {"amount": "0", "currency": "usd", "crypto": "0"}, "total": {"amount": "0.201", "currency": "usd", "crypto": "0.201"} }, "from_address": "TMDKznuDWaZwfZHcM61FYFRx9dAVPndpNv", "to_address": "TXHXiQ2aXYqtz3y9gEGiVjzxXxXechdLwE", "status": "success", "timestamp": 1746984100 } } } ``` Callbacks go to the address-specific `webhook_url` when set, otherwise to the merchant's default callback URL. Verify `X-Signature` against the exact raw body bytes before parsing JSON. `X-Idempotency-Key` is included only when the delivery path supplies one, so consumers should also deduplicate by stable business identifiers such as `data.transaction.id`. AML rejection uses the separate external event `aml.rejected.static_wallet`. See [Webhooks](https://docs.coinssend.com/webhooks.md) for payloads, signature verification, delivery behavior, and the complete event list. --- Source: https://docs.coinssend.com/withdrawals.md # Withdrawals API This document describes the endpoints for initiating cryptocurrency withdrawals.
Important: To use this API, you must first enable the "Allow Withdrawals" setting in your merchant dashboard. Withdrawal requests will be rejected if this setting is not enabled.
## Initiate Withdrawal Initiates a withdrawal of funds from your merchant account to an external wallet address. ### Request ``` POST /v1/withdrawals ``` ### Headers | Name | Required | Description | | -------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------- | | `Content-Type` | Yes | Must be `application/json` | | `Merchant` | Yes | Your merchant ID | | `Timestamp` | Yes | Unix timestamp in seconds. Must be within 5 minutes of server time. `X-Timestamp` is also accepted. | | `Sign` | Yes | HMAC-SHA256 withdrawal signature (see [Authentication](https://docs.coinssend.com/authentication.md#timestamped-hmac-signature)) | Generate the canonical JSON body described in [Authentication](https://docs.coinssend.com/authentication.md), sign it, and send that same string as the request body: ```text Sign = hmac_sha256(base64(canonical_json_body) + "." + Timestamp, API_KEY) ``` ### Request Parameters | Parameter | Type | Required | Description | | ------------ | ------ | -------- | ----------------------------------------------------------------------------------------------------------------------- | | `network` | string | Yes | Blockchain network (e.g., "tron", "bsc") | | `coin` | string | Yes | Cryptocurrency (e.g., "usdt", "usdc") | | `amount` | string | Yes | Amount to withdraw in actual value (the minimum depends on the selected asset/network pair, e.g., "50.5" for 50.5 USDT) | | `to_address` | string | Yes | Destination wallet address | > **Asset catalog:** Use `GET /v1/coins-and-fee` to fetch the currently enabled > coin/network pairs, network fees, and minimum withdrawal values. These values > can change. The withdrawal endpoint remains the final validation > authority for the pair and amount. See [Supported Coins & Networks](https://docs.coinssend.com/supported-coins.md) for the coin/network tables and guidance on displaying withdrawal options. ### Example Request ```json { "network": "tron", "coin": "usdt", "amount": "50.75", "to_address": "TXHXiQ2aXYqtz3y9gEGiVjzxXxXechdLwE" } ```
Note: Minimum and maximum withdrawal amounts depend on the current asset/network settings. Do not embed example thresholds as permanent constants.
### Example Response ```json { "data": { "success": true, "withdrawal_id": "5118a064-d5b9-4fab-ad14-8cf42f0020de", "message": "Withdrawal request submitted successfully", "status": "pending", "amounts": { "requested": { "crypto": "100.00", "usd": "100.00" }, "net": { "crypto": "98.15", "usd": "98.15" } }, "fees": { "merchant": { "crypto": "0.45", "usd": "0.45" }, "network": { "crypto": "1.40", "usd": "1.40" }, "total": { "crypto": "1.85", "usd": "1.85" } }, "fee_payer": { "merchant_fee": false, "network_fee": false } }, "status": "success" } ``` ### Response Field Descriptions | Field | Type | Description | | ------------------------------- | ------- | ------------------------------------------------------------------ | | `data` | object | Response data container | | `data.success` | boolean | Whether the withdrawal request succeeded | | `data.withdrawal_id` | string | Unique withdrawal identifier | | `data.message` | string | Human-readable message | | `data.status` | string | Current withdrawal status (pending, processing, completed, failed) | | `data.amounts` | object | Requested and net withdrawal amounts | | `data.amounts.requested.crypto` | string | Requested amount in cryptocurrency | | `data.amounts.requested.usd` | string | Requested amount in USD | | `data.amounts.net.crypto` | string | Net amount after fees in cryptocurrency | | `data.amounts.net.usd` | string | Net amount after fees in USD | | `data.fees` | object | Fee breakdown | | `data.fees.merchant.crypto` | string | Merchant fee in cryptocurrency | | `data.fees.merchant.usd` | string | Merchant fee in USD | | `data.fees.network.crypto` | string | Network fee in cryptocurrency | | `data.fees.network.usd` | string | Network fee in USD | | `data.fees.total.crypto` | string | Total fee in cryptocurrency | | `data.fees.total.usd` | string | Total fee in USD | | `data.fee_payer.merchant_fee` | boolean | Whether merchant pays the merchant fee | | `data.fee_payer.network_fee` | boolean | Whether merchant pays the network fee | | `status` | string | Overall response status ("success" or "error") | If your merchant account has the "Merchant Pays" settings enabled, the fields in `fee_payer` would be `true` and the `amounts.net.crypto` would equal the requested `amounts.requested.crypto`. ### Error Responses | HTTP status | Response shape | Description | | ----------- | ---------------------------------------------------------------- | --------------------------------------------------------------- | | `400` | `{"error":"Missing headers"}` or `{"error":"Missing timestamp"}` | Required authentication input is missing | | `400` | endpoint error with `status` and `message` | Includes withdrawal-permission denial and balance/fee rejection | | `401` | authentication `{"error":"..."}` | Invalid merchant, signature, or expired timestamp | | `403` | authentication or endpoint error | Merchant status blocks withdrawals | | `422` | validation error with `errors`, `status`, and `message` | Pair, amount, or destination validation failed | ### Example Error Response ```json { "status": "error", "message": "Insufficient available balance" } ``` ### Withdrawal Limits Each asset/network pair has current minimum and maximum amounts. Fetch the current discovery catalog immediately before presenting withdrawal choices and handle `422` as the authoritative answer if configuration changes between discovery and submission. The public schema intentionally does not publish a closed asset enum or static limit table. ### Merchant Settings for Withdrawals To use the Withdrawals API, you must first enable withdrawals in your merchant settings by checking the "**Allow Withdrawals**" checkbox. The description in settings reads: "Enable this to allow withdrawals via API". This setting is required before any withdrawal API calls will work. Additionally, there are two important settings that control how withdrawal fees are handled: 1. **"Pay Withdrawal Network Fee"** - The description in settings reads: "Enable this if merchant will pay withdrawal network fee". When enabled, the network fee is deducted from the merchant's balance instead of the withdrawal amount. 2. **"Pay Withdrawal Service Fee"** - The description in settings reads: "Enable this if merchant will pay withdrawal service fee". When enabled, the merchant fee is deducted from the merchant's balance instead of the withdrawal amount. If both checkboxes are enabled, the recipient will receive exactly the amount specified in the API request, as all fees will be deducted from your merchant balance instead of the withdrawal amount. ### Withdrawal Fees When initiating withdrawals, two types of fees apply: 1. **Network Fee**: A fixed fee for processing transactions on the blockchain 2. **Merchant Fee**: A percentage-based fee applied to the withdrawal amount #### Fee Discovery Use `GET /v1/merchants/fees` for merchant-specific percentages and fee-payer settings, and `GET /v1/coins-and-fee` for current network-fee discovery. The accepted withdrawal response is the authoritative fee snapshot for that withdrawal. #### Fee Calculation Examples The following arithmetic is illustrative only; substitute live fee values. **Scenario 1: Recipient Amount Is Reduced by Fees** When both "Pay Withdrawal Service Fee" and "Pay Withdrawal Network Fee" checkboxes are disabled: ``` Withdrawal amount: 100 USDT Network fee: 1.4 USDT (fixed) Merchant fee: 100 × 0.45% = 0.45 USDT Total fees: 1.4 + 0.45 = 1.85 USDT Amount sent to destination: 100 - 1.85 = 98.15 USDT Deducted from merchant balance: 0 USDT ``` **Scenario 2: Merchant Pays All Fees** When both "Pay Withdrawal Service Fee" and "Pay Withdrawal Network Fee" checkboxes are enabled: ``` Withdrawal amount: 100 USDT Network fee: 1.4 USDT (fixed) Merchant fee: 100 × 0.45% = 0.45 USDT Total fees: 1.4 + 0.45 = 1.85 USDT Amount sent to destination: 100 USDT (full amount) Deducted from merchant balance: 1.85 USDT (all fees) ``` The response includes the fields `fee_payer.merchant_fee` and `fee_payer.network_fee` to indicate which fee payment options are active for your merchant account. These correspond directly to the "Pay Withdrawal Service Fee" and "Pay Withdrawal Network Fee" settings. ### Additional Notes - Withdrawals are processed asynchronously - The HTTP `200` response contains the current projected status, commonly `pending` or `processing`; it is not final settlement - You will receive `withdrawal.in_progress`, `withdrawal.success`, or `withdrawal.failed` webhooks - The `to_address` must be valid for the selected network - Always double-check the destination address before initiating a withdrawal - There is no request `Idempotency-Key`. If the outcome is unknown, reconcile by the returned/stored withdrawal ID or contact support instead of automatically submitting the same withdrawal again. ## Code Examples Generate withdrawal signatures only in trusted server-side code. Do not expose merchant API keys in browsers, mobile apps, or public frontend bundles.
```php 'tron', 'coin' => 'usdt', 'amount' => '50.75', 'to_address' => 'TXHXiQ2aXYqtz3y9gEGiVjzxXxXechdLwE' ]; // Generate signature $jsonData = json_encode($withdrawalData); $base64Data = base64_encode($jsonData); $timestamp = time(); $signature = hash_hmac('sha256', $base64Data . '.' . $timestamp, $apiKey); // Set up request $ch = curl_init('https://api.coinssend.com/v1/withdrawals'); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonData); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json', 'Merchant: ' . $merchantId, 'Timestamp: ' . $timestamp, 'Sign: ' . $signature ]); // Execute request $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); // Process response if ($httpCode >= 200 && $httpCode < 300) { $responseData = json_decode($response, true); if ($responseData['data']['success'] === true) { echo "Withdrawal initiated successfully!\n"; echo "Withdrawal ID: " . $responseData['data']['withdrawal_id'] . "\n"; echo "Status: " . $responseData['data']['status'] . "\n"; echo "Message: " . $responseData['data']['message'] . "\n"; } } else { $errorData = json_decode($response, true); echo "Error: " . ($errorData['error'] ?? $errorData['message'] ?? 'Unknown error') . "\n"; } ``` ```javascript // API credentials const merchantId = 'your_merchant_id'; const apiKey = 'your_api_key'; // Withdrawal data const withdrawalData = { 'network': 'tron', 'coin': 'usdt', 'amount': '50.75', 'to_address': 'TXHXiQ2aXYqtz3y9gEGiVjzxXxXechdLwE' }; const crypto = require('node:crypto'); // Generate signature const jsonData = JSON.stringify(withdrawalData) .replace(/\//g, '\\/') .replace(/[\u0080-\uFFFF]/g, character => `\\u${character.charCodeAt(0).toString(16).padStart(4, '0')}` ); const base64Data = Buffer.from(jsonData).toString('base64'); const timestamp = Math.floor(Date.now() / 1000).toString(); const signature = crypto .createHmac('sha256', apiKey) .update(`${base64Data}.${timestamp}`) .digest('hex'); // Make the API request fetch('https://api.coinssend.com/v1/withdrawals', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Merchant': merchantId, 'Timestamp': timestamp, 'Sign': signature }, body: jsonData }) .then(response => { if (!response.ok) { return response.json().then(errorData => { throw new Error(errorData.error || errorData.message || `HTTP error! status: ${response.status}`); }); } return response.json(); }) .then(data => { if (data.data.success) { console.log("Withdrawal initiated successfully!"); console.log(`Withdrawal ID: ${data.data.withdrawal_id}`); console.log(`Status: ${data.data.status}`); console.log(`Message: ${data.data.message}`); } }) .catch(error => { console.error('Withdrawal error:', error.message); }); ``` ```python import requests import json import base64 import hashlib import hmac import time # API credentials merchant_id = 'your_merchant_id' api_key = 'your_api_key' # Withdrawal data withdrawal_data = { 'network': 'tron', 'coin': 'usdt', 'amount': '50.75', 'to_address': 'TXHXiQ2aXYqtz3y9gEGiVjzxXxXechdLwE' } def canonical_json(payload): encoded = json.dumps( payload, ensure_ascii=False, separators=(',', ':'), allow_nan=False ) escaped = [] for character in encoded: codepoint = ord(character) if character == '/': escaped.append(r'\/') elif codepoint < 0x80: escaped.append(character) elif codepoint <= 0xFFFF: escaped.append(f'\\u{codepoint:04x}') else: codepoint -= 0x10000 escaped.append(f'\\u{0xD800 + (codepoint >> 10):04x}') escaped.append(f'\\u{0xDC00 + (codepoint & 0x3FF):04x}') return ''.join(escaped) # Generate signature from the exact body that will be sent json_data = canonical_json(withdrawal_data) base64_data = base64.b64encode(json_data.encode()).decode() timestamp = str(int(time.time())) signature = hmac.new( api_key.encode(), f'{base64_data}.{timestamp}'.encode(), hashlib.sha256 ).hexdigest() # Set up headers headers = { 'Content-Type': 'application/json', 'Merchant': merchant_id, 'Timestamp': timestamp, 'Sign': signature } # Make the API request try: response = requests.post( 'https://api.coinssend.com/v1/withdrawals', headers=headers, data=json_data ) # Check for HTTP errors response.raise_for_status() # Parse the response response_data = response.json() if response_data.get('data', {}).get('success'): print("Withdrawal initiated successfully!") print(f"Withdrawal ID: {response_data['data']['withdrawal_id']}") print(f"Status: {response_data['data']['status']}") print(f"Message: {response_data['data']['message']}") else: print(f"Error: {response_data.get('message', 'Unknown error')}") except requests.exceptions.HTTPError as e: error_data = {} try: error_data = response.json() except: pass error_message = error_data.get('error') or error_data.get('message') or str(e) print(f"HTTP Error: {error_message}") except requests.exceptions.RequestException as e: print(f"Request Error: {str(e)}") ```
For more details on working with withdrawals, see [the Node.js withdrawal example](https://docs.coinssend.com/examples/node-withdrawal.md). --- Source: https://docs.coinssend.com/webhooks.md # Webhooks CoinsSend uses webhooks to notify your application about events that happen in your merchant account. Webhooks are HTTP callbacks that receive notification payloads when events occur. ## Setting Up Webhooks You can set up a webhook URL in your merchant settings. This URL will receive POST requests when specific events occur. ## Authentication For security, all webhook requests include an HMAC signature in the `X-Signature` header. You should validate this signature to ensure the webhook was sent by CoinsSend. The signature is generated using HMAC-SHA256, with your API key as the secret: ```php // PHP example for validating a webhook signature function isValidSignature($payload, $signature, $secretKey) { $computedSignature = hash_hmac('sha256', $payload, $secretKey); return hash_equals($computedSignature, $signature); } // Usage $payload = file_get_contents('php://input'); $signature = $_SERVER['HTTP_X_SIGNATURE']; $secretKey = 'your_api_key'; if (!isValidSignature($payload, $signature, $secretKey)) { http_response_code(401); echo json_encode(['error' => 'Invalid signature']); exit; } ``` Important: calculate the HMAC from the exact raw request body bytes that were received. Do not decode the JSON and re-encode it before verification, because key order, spacing, and escaping may change the payload and produce a different signature. This raw-body rule is specific to **receiving webhooks**. Merchant API request signing uses the separate canonical-JSON contract in [Authentication](https://docs.coinssend.com/authentication.md). > **AI assistant prompt:** Implement a CoinsSend webhook receiver that preserves the raw request body before JSON parsing. > Read `X-Signature`, verify it with HMAC-SHA256 and the merchant API key, then parse the payload only after the signature passes. > Add idempotent processing for duplicate deliveries and persist outcomes for `invoice.paid`, `invoice.expired`, and `aml.rejected.invoice`. > Use [AI Integration Prompts](https://docs.coinssend.com/ai-integration-prompts.md) for the full webhook receiver prompt. ## Webhook Events The externally emitted `event` values are: - `invoice.paid` - `invoice.expired` - `wallet.transaction` - `aml.rejected.invoice` - `aml.rejected.static_wallet` - `withdrawal.in_progress` - `withdrawal.success` - `withdrawal.failed` - `test` Use only the public event values listed above when routing webhook payloads. ### Fee Payer Information For invoice webhooks (`invoice.paid` and `invoice.expired`), the `fee_payer` object indicates who pays each fee: - `merchant_fee`: `true` if customer pays, `false` if merchant pays - `network_fee`: `true` if customer pays, `false` if merchant pays For withdrawal webhooks the boolean direction is different: `true` means the merchant pays that withdrawal fee. Treat invoice and withdrawal payloads as event-discriminated schemas rather than sharing one ambiguous `fee_payer` type. ### `invoice.paid` Sent when an invoice is paid. ```json { "event": "invoice.paid", "timestamp": 1746984000, "data": { "invoice_id": "12345678-1234-1234-1234-123456789012", "order_id": "order_12345", "code": "invoice_code", "status": "paid", "amounts": { "requested": { "amount": "100.50", "currency": "usd", "crypto": "100.5" }, "paid": { "amount": "100.50", "currency": "usd", "crypto": "100.5" } }, "fees": { "merchant": { "amount": "2.0100", "currency": "usd", "crypto": "2.0100" }, "network": { "amount": "1.4000", "currency": "usd", "crypto": "1.4000" }, "total": { "amount": "3.4100", "currency": "usd", "crypto": "3.4100" } }, "payment": { "coin": "usdt", "network": "tron", "address": "TXHXiQ2aXYqtz3y9gEGiVjzxXxXechdLwE", "rate": "1.0001" }, "fee_payer": { "merchant_fee": true, "network_fee": true }, "created_at": "2025-01-01T11:30:00Z", "paid_at": "2025-01-01T12:00:00Z" } } ``` ### `invoice.expired` Sent when an invoice expires without being paid (after 24 hours).
Important: The payment field is optional and will only be present if a payment method was selected before the invoice expired.
```json { "event": "invoice.expired", "timestamp": 1746984000, "data": { "invoice_id": "12345678-1234-1234-1234-123456789012", "order_id": "order_12345", "code": "invoice_code", "status": "expired", "amounts": { "requested": { "amount": "100.50", "currency": "usd", "crypto": "100.5" }, "paid": { "amount": "0", "currency": "usd", "crypto": "0" } }, "fees": { "merchant": { "amount": "0", "currency": "usd", "crypto": "0" }, "network": { "amount": "0", "currency": "usd", "crypto": "0" }, "total": { "amount": "0", "currency": "usd", "crypto": "0" } }, "payment": { "coin": "usdt", "network": "tron", "address": "TXHXiQ2aXYqtz3y9gEGiVjzxXxXechdLwE", "rate": "1.0001" }, "fee_payer": { "merchant_fee": true, "network_fee": true }, "created_at": "2025-01-01T11:30:00Z", "expired_at": "2025-01-02T11:30:00Z" } } ``` ### `wallet.transaction` Sent when a static wallet receives a deposit. ```json { "event": "wallet.transaction", "timestamp": 1746984120, "data": { "wallet": { "address": "TXHXiQ2aXYqtz3y9gEGiVjzxXxXechdLwE", "network": "tron", "coin": "usdt", "label": "Main wallet", "rate": "1.0001" }, "transaction": { "id": "87654321-4321-4321-4321-210987654321", "hash": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef", "block_number": 53214321, "amounts": { "gross": { "amount": "10.05", "currency": "usd", "crypto": "10.05" }, "net": { "amount": "9.849", "currency": "usd", "crypto": "9.849" } }, "fees": { "merchant": { "amount": "0.201", "currency": "usd", "crypto": "0.201" }, "network": { "amount": "0", "currency": "usd", "crypto": "0" }, "total": { "amount": "0.201", "currency": "usd", "crypto": "0.201" } }, "from_address": "TMDKznuDWaZwfZHcM61FYFRx9dAVPndpNv", "to_address": "TXHXiQ2aXYqtz3y9gEGiVjzxXxXechdLwE", "status": "success", "timestamp": 1746984100 } } } ``` ### `aml.rejected.invoice` Sent when AML screening blocks an invoice deposit. Snapshot details supplied by the AML flow are carried inside `risk_details`. ```json { "event": "aml.rejected.invoice", "timestamp": 1765472000, "data": { "wallet_address": "TXHXiQ2aXYqtz3y9gEGiVjzxXxXechdLwE", "coin": "usdt", "network": "tron", "amounts": { "amount": "100.00", "currency": "usd", "crypto": "1" }, "invoice": { "id": "5118a064-d5b9-4fab-ad14-8cf42f0020de", "code": "INV123", "order_id": "ORDER-1" }, "risk_score": 95, "risk_level": "severe", "risk_details": { "amount_smallest": "1000000", "usd_cents": "10000", "balance_snapshot": "1000000", "source": "misttrack" } } } ``` ### `aml.rejected.static_wallet` Sent when AML blocks a static-wallet deposit. Snapshot details supplied by the AML flow are carried inside `risk_details`. ```json { "event": "aml.rejected.static_wallet", "timestamp": 1765472000, "data": { "wallet_address": "TXHXiQ2aXYqtz3y9gEGiVjzxXxXechdLwE", "coin": "usdt", "network": "tron", "amounts": { "amount": "250.00", "currency": "usd", "crypto": "2.5" }, "static_wallet": { "id": "87654321-4321-4321-4321-210987654321", "label": "USDT Payment Address" }, "risk_score": 87, "risk_level": "high", "risk_details": { "amount_smallest": "2500000", "usd_cents": "25000", "balance_snapshot": "2500000", "source": "misttrack" } } } ``` ### `withdrawal.in_progress` Sent when a withdrawal request is being processed. ```json { "event": "withdrawal.in_progress", "timestamp": 1746984100, "data": { "withdrawal_id": "5118a064-d5b9-4fab-ad14-8cf42f0020de", "status": "processing", "amounts": { "requested": { "amount": "5.0000", "currency": "usd", "crypto": "5" }, "net": { "amount": "5.0000", "currency": "usd", "crypto": "5" } }, "fees": { "merchant": { "amount": "0.0225", "currency": "usd", "crypto": "0.0225" }, "network": { "amount": "1.4000", "currency": "usd", "crypto": "1.4" }, "total": { "amount": "1.4225", "currency": "usd", "crypto": "1.4225" } }, "payment": { "coin": "usdt", "network": "tron", "to_address": "TLbqtn4EPerCj5a3TmbSffadYgDZ3UEmoe", "rate": "1.0001" }, "created_at": "2025-05-11T17:20:50+00:00", "updated_at": "2025-05-11T17:21:40+00:00", "transaction_hash": "22255482649487ca9f6f70a618c5b2e5e93399c09fd85ab589eff20cb0996e34", "fee_payer": { "merchant_fee": false, "network_fee": false } } } ``` ### `withdrawal.success` Sent when a withdrawal has been successfully completed. ```json { "event": "withdrawal.success", "timestamp": 1746984108, "data": { "withdrawal_id": "5118a064-d5b9-4fab-ad14-8cf42f0020de", "status": "completed", "amounts": { "requested": { "amount": "5.0000", "currency": "usd", "crypto": "5" }, "net": { "amount": "5.0000", "currency": "usd", "crypto": "5" } }, "fees": { "merchant": { "amount": "0.0225", "currency": "usd", "crypto": "0.0225" }, "network": { "amount": "1.4000", "currency": "usd", "crypto": "1.4" }, "total": { "amount": "1.4225", "currency": "usd", "crypto": "1.4225" } }, "payment": { "coin": "usdt", "network": "tron", "to_address": "TLbqtn4EPerCj5a3TmbSffadYgDZ3UEmoe", "rate": "1.0001" }, "created_at": "2025-05-11T17:20:50+00:00", "transaction_hash": "934cc03cfac6a1fe99be9156fde1807cfbe74e1ed7fe5e184596b932b7c9f30a", "updated_at": "2025-05-11T17:21:48+00:00", "fee_payer": { "merchant_fee": false, "network_fee": false } } } ``` ### `withdrawal.failed` Sent when a withdrawal has failed. ```json { "event": "withdrawal.failed", "timestamp": 1746984110, "data": { "withdrawal_id": "5118a064-d5b9-4fab-ad14-8cf42f0020de", "status": "failed", "amounts": { "requested": { "amount": "5.0000", "currency": "usd", "crypto": "5" }, "net": { "amount": "5.0000", "currency": "usd", "crypto": "5" } }, "fees": { "merchant": { "amount": "0.0225", "currency": "usd", "crypto": "0.0225" }, "network": { "amount": "1.4000", "currency": "usd", "crypto": "1.4" }, "total": { "amount": "1.4225", "currency": "usd", "crypto": "1.4225" } }, "payment": { "coin": "usdt", "network": "tron", "to_address": "TLbqtn4EPerCj5a3TmbSffadYgDZ3UEmoe", "rate": "1.0001" }, "created_at": "2025-05-11T17:20:50+00:00", "updated_at": "2025-05-11T17:21:50+00:00", "error_message": "Insufficient energy on network", "fee_payer": { "merchant_fee": false, "network_fee": false } } } ``` ## Responding to Webhooks Your server should respond with a 2xx HTTP status to acknowledge receipt. A non-2xx response or transport failure can result in another delivery. The exact attempt count and schedule are not guaranteed, so do not depend on a fixed retry count. ### Delivery Idempotency Withdrawal events currently include a stable header: ```text X-Idempotency-Key: withdrawal:{withdrawal_id}:{event} ``` The same key is reused across withdrawal delivery attempts and manual resends. Invoice, wallet, AML, and test events do not currently guarantee this header. For those events, store a receiver-side deduplication key built from the event and its stable entity identifier where available. Make processing idempotent even when the header is absent. ## Testing Webhooks You can test your webhook integration from your merchant dashboard using the "Test Webhook" feature. This will send a test webhook to your configured webhook URL with sample data. ### Test Webhook Payload The dashboard sends a simple payload when you trigger a test: ```json { "event": "test", "timestamp": 1746984000, "data": { "merchant_id": "12345678-1234-1234-1234-123456789012", "test": true, "message": "This is a test webhook to verify your integration / - ё" } } ``` ## Webhook Delivery - Webhooks are delivered in real-time as events occur - Delivery can be attempted more than once; the exact retry policy is not a public constant - Delivery outcomes are logged for operational review - Failed webhooks can be manually resent from the merchant dashboard ## Best Practices 1. **Respond Quickly**: Your webhook endpoint should respond as quickly as possible, ideally within a few seconds. 2. **Process Asynchronously**: To ensure quick responses, process the webhook data asynchronously after acknowledging receipt. 3. **Verify Signatures**: Always verify the webhook signature to ensure the request is legitimate. 4. **Handle Duplicate Events**: Use `X-Idempotency-Key` when present and an event/entity deduplication key otherwise. 5. **Monitor Webhook Failures**: Regularly check your merchant dashboard for failed webhook deliveries. 6. **Test Your Integration**: Use the test webhook feature to ensure your endpoint properly handles and processes webhook data. --- Source: https://docs.coinssend.com/merchants.md # Merchant API This document describes endpoints related to merchant account management, balances, and statistics. ## Get Merchant Balances Retrieves the current cryptocurrency balances for a merchant, including available and frozen amounts with USD conversions. ### Request ``` GET /v1/merchants/balances ``` ### Headers | Name | Required | Description | | -------------- | -------- | -------------------------------------------------------------------------------------- | | `Content-Type` | Yes | Must be `application/json` | | `Merchant` | Yes | Your merchant ID | | `Sign` | Yes | Request signature (see [Authentication](https://docs.coinssend.com/authentication.md)) | ### Example Response ```json { "status": "success", "data": { "total_balance_usd": "62.2798", "total_frozen_balance_usd": "0.0000", "total_available_balance_usd": "62.2798", "balances": [ { "network": "tron", "coin": "usdt", "balance": "7.85938000", "available_balance": "7.85938000", "frozen_balance": "0.00000000", "coin_exchange_frozen_balance": "0.00000000", "usd_rate": "1.0003677165865", "usd_value": "7.8623", "frozen_usd_value": "0.0000", "coin_exchange_frozen_usd_value": "0.0000", "available_usd_value": "7.8623" }, { "network": "bsc", "coin": "usdt", "balance": "4.45500000", "available_balance": "4.45500000", "frozen_balance": "0.00000000", "coin_exchange_frozen_balance": "0.00000000", "usd_rate": "1.0003677165865", "usd_value": "4.4566", "frozen_usd_value": "0.0000", "coin_exchange_frozen_usd_value": "0.0000", "available_usd_value": "4.4566" } ] } } ``` ### Response Field Descriptions | Field | Type | Description | | ------------------------------------------- | ------ | --------------------------------------------------------------------------------------- | | `total_balance_usd` | string | Total balance across all coins and networks converted to USD | | `total_frozen_balance_usd` | string | Total frozen balance across all coins and networks converted to USD | | `total_available_balance_usd` | string | Total available balance (total - frozen) across all coins and networks converted to USD | | `balances` | array | Array of balance objects for each coin/network combination | | `balances[].network` | string | Blockchain network (e.g., "tron", "bsc") | | `balances[].coin` | string | Cryptocurrency symbol (e.g., "usdt", "usdc") | | `balances[].balance` | string | Total balance in human-readable format | | `balances[].available_balance` | string | Available balance in human-readable format (total - frozen) | | `balances[].frozen_balance` | string | Frozen balance in human-readable format (funds that cannot be withdrawn yet) | | `balances[].coin_exchange_frozen_balance` | string | Amount reserved for in-progress exchanges | | `balances[].usd_rate` | string | Current USD exchange rate for this coin | | `balances[].usd_value` | string | Total balance converted to USD | | `balances[].frozen_usd_value` | string | Frozen balance converted to USD | | `balances[].coin_exchange_frozen_usd_value` | string | Exchange-reserved balance converted to USD | | `balances[].available_usd_value` | string | Available balance converted to USD | ### Notes - Frozen balances represent funds from recently received deposits that are on hold for 24 hours. This hold period is necessary for Anti-Money Laundering (AML) compliance checks and to prevent fraudulent transactions. - `coin_exchange_frozen_balance` reflects funds currently locked by automatic exchange jobs. They are excluded from `available_balance` until processing completes. - Available balance is the portion that can be withdrawn immediately (`balance - frozen_balance - coin_exchange_frozen_balance`). - USD values are calculated using current exchange rates. - The response may include zeroed entries for supported coins/networks even when no historical balance exists, allowing frontends to render consistent tables. ## Get Merchant Fees Returns the current fee settings for your merchant account. ### Request ``` GET /v1/merchants/fees ``` ### Headers | Name | Required | Description | | -------------- | -------- | -------------------------------------------------------------------------------------- | | `Content-Type` | Yes | Must be `application/json` | | `Merchant` | Yes | Your merchant ID | | `Sign` | Yes | Request signature (see [Authentication](https://docs.coinssend.com/authentication.md)) | ### Example Response ```json { "status": "success", "data": { "service_fee_percent": "2.5", "static_wallet_fee_percent": "1.0", "withdrawal_fee_percent": "1.5", "pay_withdrawal_service_fee": true, "pay_withdrawal_network_fee": true, "is_auto_exchange": false, "auto_exchange_coin": null, "auto_exchange_network": null, "auto_exchange_min_usd_amount": null, "invoice_underpayment_percent": "1.0000", "invoice_underpayment_max_amount": null, "invoice_underpayment_max_currency": null, "network_fees": [ { "network": "tron", "coin": "usdt", "network_fee": "1" } ] } } ``` ### Response Field Descriptions | Field | Type | Description | | | ----------------------------------- | ------- | -------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | | `service_fee_percent` | string | Fee percentage applied to invoice payments | | | `static_wallet_fee_percent` | string | Fee percentage for static wallet deposits | | | `withdrawal_fee_percent` | string | Fee percentage for withdrawals | | | `pay_withdrawal_service_fee` | boolean | Whether merchant pays the service fee when withdrawing | | | `pay_withdrawal_network_fee` | boolean | Whether merchant pays the network fee when withdrawing | | | `is_auto_exchange` | boolean | Whether automatic exchange is enabled for new deposits | | | `auto_exchange_coin` | string | null | Target coin used for automatic exchanges when enabled | | `auto_exchange_network` | string | null | Target network for automatic exchanges when enabled | | `auto_exchange_min_usd_amount` | string | null | Minimum USD amount required to trigger an automatic exchange | | `invoice_underpayment_percent` | string | Allowed underpayment percentage between 0 and 90 as a decimal. Both `12.2` and `12,2` mean `12.2%` | | | `invoice_underpayment_max_amount` | string | Maximum underpayment amount in USD (e.g., `1.1` or `1,1` means `1.1 USD`) | | | `invoice_underpayment_max_currency` | string | Currency of the maximum underpayment amount. Only `USD` is allowed and it defaults to this value when omitted. | | | `network_fees` | array | List of supported coins and their network fees | | `invoice_underpayment_max_amount` may only be provided when `invoice_underpayment_percent` is included in the request. The currency is always `USD` and defaults to this value if omitted. --- Source: https://docs.coinssend.com/coin-rates.md # Coin Rates API For a readable coin/network catalog, API identifiers, and payment selector guidance, start with [Supported Coins & Networks](https://docs.coinssend.com/supported-coins.md). Retrieves current exchange rates and asset/network metadata. USD is the fiat baseline used by this response, but consumers must locate entries by `coin.symbol` rather than depending on array order. Assets without usable rate data may be absent. Rates, quotes, networks, limits, fees, and precision can change without a schema version change. ## Get Coin Rates {#get-coin-rates} ### Request ``` GET /v1/get-coin-rate ``` ### Example Response This is an illustrative response shape, not a static price or availability contract. ```json { "status": "success", "data": [ { "coin": { "symbol": "usd", "name": "US Dollar", "type": "fiat" }, "quotes": [ { "currency_to": "usdt", "price": "1", "type": "crypto" }, { "currency_to": "usdc", "price": "1", "type": "crypto" }, { "currency_to": "trx", "price": "8.333333333333", "type": "crypto" }, { "currency_to": "bnb", "price": "0.001723692148", "type": "crypto" }, { "currency_to": "eth", "price": "0.000320466599", "type": "crypto" } ], "networks": [] }, { "coin": { "symbol": "usdt", "name": "Tether", "type": "crypto" }, "quotes": [ { "currency_to": "usd", "price": "1", "type": "fiat" }, { "currency_to": "trx", "price": "8.3167", "type": "crypto" }, { "currency_to": "bnb", "price": "0.00172", "type": "crypto" }, { "currency_to": "usdc", "price": "1", "type": "crypto" } ], "networks": [ { "network": "tron", "name": "TRON (TRC-20)", "type": "crypto", "deposit_enabled": true, "withdraw_enabled": true, "min_deposit": "5", "min_withdrawal": "5", "max_withdrawal": "1000", "network_fee": "1.40", "decimals": 6 } ] }, { "coin": { "symbol": "eth", "name": "Ethereum", "type": "crypto" }, "quotes": [ { "currency_to": "usd", "price": "3120.45", "type": "fiat" }, { "currency_to": "bnb", "price": "10.215", "type": "crypto" }, { "currency_to": "usdt", "price": "3120.45", "type": "crypto" } ], "networks": [ { "network": "eth", "name": "Ethereum (ERC-20)", "type": "crypto", "deposit_enabled": true, "withdraw_enabled": true, "min_deposit": "0.001", "min_withdrawal": "0.003", "max_withdrawal": "10", "network_fee": "0.0002", "decimals": 18 } ] } ] } ``` ### Response Field Descriptions | Field | Type | Description | | | ----------------------------- | ------- | -------------------------------------------------------------------- | ---------------------------------------------- | | `coin.symbol` | string | Coin ticker symbol | | | `coin.name` | string | Human readable name | | | `coin.type` | string | `crypto` or `fiat`, describing the asset class | | | `quotes[].currency_to` | string | Currency code the rate is quoted against (e.g. `usd`, `usdt`, `trx`) | | | `quotes[].price` | string | Price quoted against `currency_to` | | | `quotes[].type` | string | Indicates whether the quote currency is `fiat` or `crypto` | | | `networks[].network` | string | Network identifier (e.g. `tron`, `eth`, `bsc`) | | | `networks[].name` | string | Human-readable network name and token standard | | | `networks[].type` | string | Asset type on the network (`crypto` or `fiat`) | | | `networks[].deposit_enabled` | boolean | Indicates if deposits are currently allowed | | | `networks[].withdraw_enabled` | boolean | Indicates if withdrawals are currently allowed | | | `networks[].min_deposit` | string | null | Minimum deposit amount accepted on the network | | `networks[].min_withdrawal` | string | null | Minimum withdrawal amount allowed | | `networks[].max_withdrawal` | string | null | Maximum withdrawal amount allowed | | `networks[].network_fee` | string | null | Network fee charged in coin units | | `networks[].decimals` | integer | null | Number of decimals supported on-chain | ## Get Supported Coins & Fee {#get-supported-coins-and-fee} Use this public endpoint for live asset discovery. It excludes hidden pairs and pairs where both deposits and withdrawals are disabled. Individual write endpoints still own final pair/amount validation, so handle a later `422` even after successful discovery. ### Request ``` GET /v1/coins-and-fee ``` ### Example Response Values below are examples only. Do not copy them into a closed enum or fee table. ```json { "status": "success", "data": { "coins": [ { "coin": { "symbol": "USDT", "label": "USDT" }, "networks": [ { "network": "TRON", "title": "TRON (TRC-20)", "network_fee": "1.4", "service_fee": "0.45", "min_withdrawal": "5" }, { "network": "ETH", "title": "Ethereum (ERC-20)", "network_fee": "1", "service_fee": "0.45", "min_withdrawal": "5" } ] }, { "coin": { "symbol": "BTC", "label": "BTC" }, "networks": [ { "network": "BITCOIN", "title": "Bitcoin", "network_fee": "0.000016", "service_fee": "0.45", "min_withdrawal": "0.0001" } ] } ] } } ``` ### Response Field Descriptions | Field | Type | Description | | | ---------------------------------------- | ------ | ------------------------------------------- | ------------------------------------------------------- | | `data.coins` | array | Supported assets grouped by coin symbol | | | `data.coins[].coin.symbol` | string | Uppercase coin symbol | | | `data.coins[].coin.label` | string | Display label for the coin | | | `data.coins[].networks` | array | Enabled networks for the coin | | | `data.coins[].networks[].network` | string | Uppercase network code | | | `data.coins[].networks[].title` | string | Human-readable network name | | | `data.coins[].networks[].network_fee` | string | null | Network fee charged in coin units | | `data.coins[].networks[].service_fee` | string | Service fee percentage applied to the asset | | | `data.coins[].networks[].min_withdrawal` | string | null | Minimum withdrawal amount for the asset on that network | The route advertises a public cache lifetime of 900 seconds. Respect that cache window, refresh before presenting a write flow when practical, and let the write endpoint remain authoritative. --- Source: https://docs.coinssend.com/error-handling.md # Error Handling CoinsSend does not use one universal error envelope and does not guarantee a symbolic `code` field. Branch on HTTP status first, then parse the applicable documented shape. ## Success Envelope Most successful operations return: ```json { "data": {}, "status": "success" } ``` Successful write operations currently return HTTP `200`, including asynchronous withdrawal submission. ## Error Shapes ### Authentication Errors Header authentication failures return only `error`: ```json { "error": "Invalid sign" } ``` Verified messages include `Missing headers`, `Missing timestamp`, `Invalid merchant`, `Invalid sign`, `Timestamp expired`, and `Merchant account is suspended`. ### Endpoint Errors Endpoint failures normally return: ```json { "status": "error", "message": "Order id already exists" } ``` Some endpoints add fields at the top level. Provider-card failures add `failure_code`; withdrawal failures can add amounts and fee details. Treat human-readable message text as descriptive rather than a stable machine code. ### Validation Validation failures use HTTP `422`: ```json { "errors": { "amount": [ "The amount field is required." ] }, "status": "error", "message": "The given data was invalid." } ``` ### Rate Limiting The wallet-creation limit returns `status` and `message`. Other HTTP `429` responses may use a different shape or include `retry_after` and a `Retry-After` header. Treat rate-limit metadata as optional; see [Rate Limits and Safe Retries](https://docs.coinssend.com/rate-limits.md). ## HTTP Statuses | Status | Meaning in the public contract | | ------ | ----------------------------------------------------------------------------------------------------- | | `400` | Missing auth input or an endpoint rejection. Withdrawal permission denial currently uses this status. | | `401` | Invalid merchant/signature or expired timestamp. | | `403` | Suspended/blocked merchant state or authorization failure. | | `404` | Public invoice or QR resource not found. | | `409` | Invoice `order_id` already exists, or wallet creation is already in progress. | | `422` | Request validation or provider-card capability/state failure. | | `429` | A rate limit was exceeded. | | `500` | Unexpected application failure. | | `502` | Provider-card checkout could not obtain a usable provider response. | ## Endpoint-Specific Cases The labels in this table are suggested **client-side categories**. They are not wire fields returned by CoinsSend. | Client category | HTTP status | Endpoint / condition | Wire shape | | ------------------------------ | ------------- | ----------------------------------------------------- | ----------------------------------------- | | `missing_headers` | `400` | Authenticated endpoint without Merchant or Sign | `{"error":"Missing headers"}` | | `missing_timestamp` | `400` | Withdrawal without Timestamp/X-Timestamp | `{"error":"Missing timestamp"}` | | `invalid_signature` | `401` | Signature mismatch | `{"error":"Invalid sign"}` | | `timestamp_expired` | `401` | Invalid or stale timestamp | `{"error":"Timestamp expired"}` | | `invoice_order_conflict` | `409` | Duplicate merchant `order_id` | endpoint error | | `validation_failed` | `422` | Invalid invoice, wallet, or withdrawal input | validation error | | `withdrawal_permission_denied` | `400` | API key lacks withdrawal permission | endpoint error | | `withdrawal_rejected` | `400` | Balance/fee/service rejection | endpoint error with possible extra fields | | `provider_card_unavailable` | usually `422` | Current availability or invoice state blocks checkout | endpoint error with `failure_code` | | `wallet_creation_in_progress` | `409` | Merchant-scoped wallet lock conflict | endpoint error | ## Robust Parsing
```php ## Retry Safety Automatically retry only verified read operations with bounded exponential backoff and jitter. A timeout on a write does not prove the server rejected it. - Duplicate invoice `order_id` returns `409`; it does not replay the first response. - Withdrawals have no request idempotency key. - Static-wallet creation can create another address when repeated. - Provider-card creation only reuses an already-active order; this is not a general idempotency contract. Reconcile an unknown write outcome instead of blindly resubmitting it. Full guidance is in [Rate Limits and Safe Retries](https://docs.coinssend.com/rate-limits.md). ## Troubleshooting Invalid Signatures - Generate the PHP-compatible canonical JSON described in [Authentication](https://docs.coinssend.com/authentication.md). - Sign and send the same immutable canonical string. - Use strings for monetary amounts. - For withdrawals, recompute a fresh timestamp and HMAC. - Run the client against [`signature-test-vectors.json`](https://docs.coinssend.com/signature-test-vectors.json), including the URL/Unicode vector. - Remember that webhook verification is different: it uses the exact raw received body bytes. The exact machine-readable response schemas are in [OpenAPI](https://docs.coinssend.com/openapi.json). --- Source: https://docs.coinssend.com/rate-limits.md # Rate Limits and Safe Retries The public contract does not promise universal `X-RateLimit-*` headers or one fixed global request count. Clients must handle HTTP `429` on any operation. ## Documented Wallet-Creation Limit `POST /v1/wallet-address` has this merchant limit: - threshold: 100 successful wallet creations; - window: 60 seconds; - key: current merchant; - failed creation attempts are not counted; - rejection: HTTP `429` with `status` and `message`. Example: ```json { "status": "error", "message": "Too many requests. Please try again later." } ``` This response does not currently guarantee `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, `Retry-After`, or `retry_after`. ## Additional Limits Additional limits may apply. Their response bodies and headers are not part of a universal contract. Clients should tolerate HTTP `429` whether or not `Retry-After` is present. ## Retry Decision Table | Operation | Automatic retry after `429` or transport failure? | Reason | | ------------------------------------------------------ | ------------------------------------------------- | -------------------------------------------------------------------------------------- | | `GET /v1/get-coin-rate` | Yes, with bounded backoff | Read-only public discovery | | `GET /v1/coins-and-fee` | Yes, with bounded backoff and caching | Read-only public discovery | | `GET /v1/merchants/balances` | Yes, with a fresh signature/timestamp when used | Read-only merchant state | | `GET /v1/merchants/fees` | Yes, with a fresh signature/timestamp when used | Read-only merchant state | | `GET /v1/invoices/{invoiceCode}` | Yes | Read-only checkout state | | `GET /v1/wallet-addresses/{walletAddress}/qr` | Yes | Read-only binary response | | `POST /v1/invoices` | No blind retry after an unknown outcome | `order_id` uniqueness returns `409`; it does not replay the original response | | `POST /v1/withdrawals` | No | No request idempotency key; timestamp freshness is not idempotency | | `POST /v1/wallet-address` | No for static wallets | A successful retry can create another address | | `POST /v1/invoices/{invoiceCode}/provider-card-orders` | Do not generalize | Only an already-active order is reused; there is no general `Idempotency-Key` contract | An HTTP response proves whether a request was rejected. A client-side timeout does not: the server may already have accepted the write. ## Backoff for Safe Reads For read operations, use bounded exponential backoff with jitter: ```javascript async function retrySafeRead(makeRequest, maxAttempts = 4) { for (let attempt = 1; attempt <= maxAttempts; attempt += 1) { const response = await makeRequest() if (response.status !== 429 || attempt === maxAttempts) { return response } const retryAfterHeader = response.headers.get('Retry-After') const retryAfter = retryAfterHeader === null ? Number.NaN : Number(retryAfterHeader) const baseDelayMs = Number.isFinite(retryAfter) ? retryAfter * 1000 : Math.min(500 * 2 ** (attempt - 1), 8000) const jitterMs = Math.floor(Math.random() * 250) await new Promise(resolve => setTimeout(resolve, baseDelayMs + jitterMs)) } } ``` For timestamp-signed reads, recompute `Timestamp` and `Sign` for each attempt. Never sleep and reuse a timestamp that can age beyond the 300-second signature window. ## Handling an Unknown Write Outcome 1. Persist the client order/reference and request intent before sending. 2. If a complete success response arrives, persist the returned CoinsSend ID. 3. If the result is unknown, do not issue a blind duplicate. 4. Reconcile using a known returned/public identifier where one exists, or investigate through merchant operations/support. 5. For withdrawals, wait for the signed withdrawal webhook when an ID was obtained; otherwise escalate the unknown outcome instead of resubmitting. ## Monitoring - Record endpoint, HTTP status, request correlation data, and `Retry-After` when present; never log the API key or full `Sign` header. - Separate application `429` responses from edge `429` responses. - Alert on sustained rate limiting and unknown outcomes for write operations. - Cache the catalog according to its advertised 900-second cache lifetime. For request-signature freshness and canonicalization, see [Authentication](https://docs.coinssend.com/authentication.md). For exact operation metadata, see [OpenAPI](https://docs.coinssend.com/openapi.json). --- Source: https://docs.coinssend.com/mcp.md # Documentation MCP Server CoinsSend exposes a read-only [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server for AI coding agents and developer tools. It serves the same reviewed public documentation and OpenAPI contract as this site. It cannot create invoices, wallets, withdrawals, or call any other business endpoint. ## Connect remotely Use this Streamable HTTP endpoint in an MCP client that supports protocol revision `2025-11-25` or an older revision negotiated by the server: ```text https://api.coinssend.com/v1/docs/mcp ``` No API key is required because the server exposes public documentation only. Requests are rate-limited. A client that requires only the newer stateless `2026-07-28` protocol revision will need a future server upgrade. For Codex, register the remote server with: ```bash codex mcp add coinssend-docs --url https://api.coinssend.com/v1/docs/mcp ``` MCP client configuration formats differ. In clients that accept a URL-based server entry, the equivalent shape is: ```json { "mcpServers": { "coinssend-docs": { "url": "https://api.coinssend.com/v1/docs/mcp" } } } ``` ## Resources | URI | Media type | Contents | | ------------------------------- | ---------------------------------------------- | ------------------------------------------------------------------------------ | | `docs://manifest` | `application/json` | Ordered allowlist of public pages and machine-readable resources | | `docs://openapi` | `application/vnd.oai.openapi+json;version=3.1` | Canonical public OpenAPI 3.1 contract | | `docs://signature-test-vectors` | `application/json` | Executable fixtures for PHP-compatible request canonicalization and signatures | The manifest contains only the public pages and resources available through the documentation server. ## Tools | Tool | Purpose | | ----------------------- | ---------------------------------------------------------------------- | | `search_docs` | Deterministic lexical search with at most eight bounded results | | `get_docs_page` | Read one exact public Markdown path returned by the manifest or search | | `get_openapi_operation` | Retrieve an operation by its stable `operationId` | | `get_openapi_schema` | Retrieve a named schema from `components.schemas` | All tools are annotated read-only, idempotent, and closed-world. They serve only the public documentation corpus and do not call the CoinsSend business API. ## Agent safety guidance - Use OpenAPI operations and schemas for request and response structure; use prose pages for workflow and operational guidance. - Treat MCP output as documentation, not as approval to move funds or perform a production action. - Do not infer idempotency. In particular, invoice creation, static-wallet creation, and withdrawal creation do not accept an `Idempotency-Key` contract. - Validate the current live coin and network catalog before constructing a transaction; supported assets and fees can change. - Keep merchant API keys outside prompts, logs, source control, and MCP arguments. For clients without MCP support, use the generated [`llms.txt`](https://docs.coinssend.com/llms.txt) index or the canonical [`openapi.json`](https://docs.coinssend.com/openapi.json) directly. --- Source: https://docs.coinssend.com/examples/README.md # CoinsSend API Examples These examples implement the verified public contract from [`openapi.json`](https://docs.coinssend.com/openapi.json). ## Examples - [PHP invoice](https://docs.coinssend.com/examples/php-invoice.md) - [Node.js invoice](https://docs.coinssend.com/examples/js-invoice.md) - [Python invoice](https://docs.coinssend.com/examples/python-invoice.md) - [PHP static wallet](https://docs.coinssend.com/examples/php-static-wallet.md) - [Node.js static wallet](https://docs.coinssend.com/examples/js-static-wallet.md) - [Python static wallet](https://docs.coinssend.com/examples/python-static-wallet.md) - [Node.js withdrawal and webhook receiver](https://docs.coinssend.com/examples/node-withdrawal.md) ## Shared Signing Rules 1. Build the request object using strings for monetary values. 2. Encode it with the PHP-compatible canonical JSON rules documented in [Authentication](https://docs.coinssend.com/authentication.md). 3. Sign `base64(canonical_body)`. 4. Send the same canonical string as the HTTP request body. 5. Use legacy MD5 only when no timestamp header is sent. 6. For withdrawals, send `Timestamp` and use timestamped HMAC-SHA256. Run client implementations against [`signature-test-vectors.json`](https://docs.coinssend.com/signature-test-vectors.json). Keep merchant API keys in backend secrets; none of these examples belongs in browser code. ## Write Safety The public API does not accept a general `Idempotency-Key` request header. Invoice `order_id` uniqueness rejects a duplicate with `409`, but does not replay the first response. Withdrawals and static-wallet creation must not be blindly retried after an unknown outcome. Provider-card creation only reuses an already-active order. Error bodies are not universal: authentication errors return `{"error":"..."}`; endpoint errors normally return `status` and `message`; validation returns `errors`, `status`, and `message`. --- Source: https://docs.coinssend.com/examples/php-invoice.md # PHP Example: Creating an Invoice ```php 'order_'.time(), 'amount' => '100.50', 'is_customer_fee' => false, 'is_customer_network_fee' => false, 'allow_card_payments' => true, 'success_url' => 'https://merchant.example/payment-success', 'cancel_url' => 'https://merchant.example/payment-cancelled', ]; // PHP default json_encode flags are the API's canonical request-body format. $body = json_encode($payload, JSON_THROW_ON_ERROR); $sign = md5(base64_encode($body).$apiKey); $handle = curl_init('https://api.coinssend.com/v1/invoices'); curl_setopt_array($handle, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $body, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'Merchant: '.$merchantId, 'Sign: '.$sign, ], ]); $rawResponse = curl_exec($handle); if ($rawResponse === false) { throw new RuntimeException(curl_error($handle)); } $httpStatus = curl_getinfo($handle, CURLINFO_HTTP_CODE); curl_close($handle); $response = json_decode($rawResponse, true, flags: JSON_THROW_ON_ERROR); if ($httpStatus < 200 || $httpStatus >= 300) { throw new RuntimeException($response['error'] ?? $response['message'] ?? "HTTP {$httpStatus}"); } printf("Invoice %s: %s\n", $response['data']['code'], $response['data']['url']); ``` Keep the API key server-side. Persist the returned invoice identifiers. A duplicate `order_id` returns `409` and does not replay the original response, so do not blindly retry an unknown outcome with a new order ID. --- Source: https://docs.coinssend.com/examples/js-invoice.md # Node.js Example: Creating an Invoice Run this code on a trusted server. Never expose the merchant API key in browser JavaScript. ```javascript const crypto = require('node:crypto') function canonicalJson(payload) { return JSON.stringify(payload) .replace(/\//g, '\\/') .replace(/[\u0080-\uFFFF]/g, character => `\\u${character.charCodeAt(0).toString(16).padStart(4, '0')}` ) } function legacySign(body, apiKey) { const payloadBase64 = Buffer.from(body, 'utf8').toString('base64') return crypto.createHash('md5').update(payloadBase64 + apiKey).digest('hex') } async function createInvoice({ merchantId, apiKey, orderId, amount }) { const payload = { order_id: orderId, amount, is_customer_fee: false, is_customer_network_fee: false, allow_card_payments: true, success_url: 'https://merchant.example/payment-success', cancel_url: 'https://merchant.example/payment-cancelled' } const body = canonicalJson(payload) const response = await fetch('https://api.coinssend.com/v1/invoices', { method: 'POST', headers: { 'Content-Type': 'application/json', Merchant: merchantId, Sign: legacySign(body, apiKey) }, body }) const result = await response.json() if (!response.ok) { throw new Error(result.error || result.message || `HTTP ${response.status}`) } return result.data } const invoice = await createInvoice({ merchantId: process.env.COINSSEND_MERCHANT_ID, apiKey: process.env.COINSSEND_API_KEY, orderId: `order_${Date.now()}`, amount: '100.50' }) console.log(invoice.code, invoice.url) ``` Persist `id`, `order_id`, `code`, and `url` before redirecting the buyer. If the request outcome is unknown, do not generate a new order ID and retry blindly. A repeated accepted `order_id` returns `409` without replaying the original invoice response. --- Source: https://docs.coinssend.com/examples/python-invoice.md # Python Example: Creating an Invoice ```python import base64 import hashlib import json import os import time import requests def canonical_json(payload): encoded = json.dumps( payload, ensure_ascii=False, separators=(',', ':'), allow_nan=False, ) escaped = [] for character in encoded: codepoint = ord(character) if character == '/': escaped.append(r'\/') elif codepoint < 0x80: escaped.append(character) elif codepoint <= 0xFFFF: escaped.append(f'\\u{codepoint:04x}') else: codepoint -= 0x10000 escaped.append(f'\\u{0xD800 + (codepoint >> 10):04x}') escaped.append(f'\\u{0xDC00 + (codepoint & 0x3FF):04x}') return ''.join(escaped) def legacy_sign(body, api_key): payload_base64 = base64.b64encode(body.encode('utf-8')).decode('ascii') return hashlib.md5((payload_base64 + api_key).encode('utf-8')).hexdigest() merchant_id = os.environ['COINSSEND_MERCHANT_ID'] api_key = os.environ['COINSSEND_API_KEY'] payload = { 'order_id': f'order_{int(time.time())}', 'amount': '100.50', 'is_customer_fee': False, 'is_customer_network_fee': False, 'allow_card_payments': True, 'success_url': 'https://merchant.example/payment-success', 'cancel_url': 'https://merchant.example/payment-cancelled', } body = canonical_json(payload) response = requests.post( 'https://api.coinssend.com/v1/invoices', headers={ 'Content-Type': 'application/json', 'Merchant': merchant_id, 'Sign': legacy_sign(body, api_key), }, data=body, timeout=15, ) result = response.json() if not response.ok: raise RuntimeError(result.get('error') or result.get('message') or f'HTTP {response.status_code}') print(result['data']['code'], result['data']['url']) ``` Use `data=body`, not `json=payload`: the canonical string is both the signed payload and the sent body. Persist the returned identifiers. A duplicate `order_id` returns `409` without replaying the original response. --- Source: https://docs.coinssend.com/examples/php-static-wallet.md # PHP Example: Creating a Static Wallet ```php 'tron', 'coin' => 'usdt', 'type' => 'static', 'label' => 'Checkout deposits', 'webhook_url' => 'https://merchant.example/coinssend/webhook', ]; $body = json_encode($payload, JSON_THROW_ON_ERROR); $sign = md5(base64_encode($body).$apiKey); $handle = curl_init('https://api.coinssend.com/v1/wallet-address'); curl_setopt_array($handle, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => $body, CURLOPT_HTTPHEADER => [ 'Content-Type: application/json', 'Merchant: '.$merchantId, 'Sign: '.$sign, ], ]); $rawResponse = curl_exec($handle); if ($rawResponse === false) { throw new RuntimeException(curl_error($handle)); } $httpStatus = curl_getinfo($handle, CURLINFO_HTTP_CODE); curl_close($handle); $response = json_decode($rawResponse, true, flags: JSON_THROW_ON_ERROR); if ($httpStatus < 200 || $httpStatus >= 300) { throw new RuntimeException($response['error'] ?? $response['message'] ?? "HTTP {$httpStatus}"); } printf("Address: %s\nQR: %s\n", $response['data']['address'], $response['data']['qr_code_url']); ``` Static-wallet creation has no request idempotency contract. A timeout does not prove that creation failed, so do not automatically repeat this POST. Fetch the live catalog instead of treating the example asset pair as permanent. --- Source: https://docs.coinssend.com/examples/js-static-wallet.md # Node.js Example: Creating a Static Wallet Run this code on a trusted server. Static-wallet creation has no request idempotency contract, so do not automatically retry an unknown outcome. ```javascript const crypto = require('node:crypto') function canonicalJson(payload) { return JSON.stringify(payload) .replace(/\//g, '\\/') .replace(/[\u0080-\uFFFF]/g, character => `\\u${character.charCodeAt(0).toString(16).padStart(4, '0')}` ) } function legacySign(body, apiKey) { const payloadBase64 = Buffer.from(body, 'utf8').toString('base64') return crypto.createHash('md5').update(payloadBase64 + apiKey).digest('hex') } async function createStaticWallet({ merchantId, apiKey, network, coin }) { const payload = { network, coin, type: 'static', label: 'Checkout deposits', webhook_url: 'https://merchant.example/coinssend/webhook' } const body = canonicalJson(payload) const response = await fetch('https://api.coinssend.com/v1/wallet-address', { method: 'POST', headers: { 'Content-Type': 'application/json', Merchant: merchantId, Sign: legacySign(body, apiKey) }, body }) const result = await response.json() if (!response.ok) { throw new Error(result.error || result.message || `HTTP ${response.status}`) } return result.data } const wallet = await createStaticWallet({ merchantId: process.env.COINSSEND_MERCHANT_ID, apiKey: process.env.COINSSEND_API_KEY, network: 'tron', coin: 'usdt' }) console.log(wallet.address, wallet.qr_code_url) ``` Discover the current pair catalog at `GET /v1/coins-and-fee`; do not treat the example pair as a permanent availability guarantee. --- Source: https://docs.coinssend.com/examples/python-static-wallet.md # Python Example: Creating a Static Wallet ```python import base64 import hashlib import json import os import requests def canonical_json(payload): encoded = json.dumps( payload, ensure_ascii=False, separators=(',', ':'), allow_nan=False, ) escaped = [] for character in encoded: codepoint = ord(character) if character == '/': escaped.append(r'\/') elif codepoint < 0x80: escaped.append(character) elif codepoint <= 0xFFFF: escaped.append(f'\\u{codepoint:04x}') else: codepoint -= 0x10000 escaped.append(f'\\u{0xD800 + (codepoint >> 10):04x}') escaped.append(f'\\u{0xDC00 + (codepoint & 0x3FF):04x}') return ''.join(escaped) def legacy_sign(body, api_key): payload_base64 = base64.b64encode(body.encode('utf-8')).decode('ascii') return hashlib.md5((payload_base64 + api_key).encode('utf-8')).hexdigest() merchant_id = os.environ['COINSSEND_MERCHANT_ID'] api_key = os.environ['COINSSEND_API_KEY'] payload = { 'network': 'tron', 'coin': 'usdt', 'type': 'static', 'label': 'Checkout deposits', 'webhook_url': 'https://merchant.example/coinssend/webhook', } body = canonical_json(payload) response = requests.post( 'https://api.coinssend.com/v1/wallet-address', headers={ 'Content-Type': 'application/json', 'Merchant': merchant_id, 'Sign': legacy_sign(body, api_key), }, data=body, timeout=15, ) result = response.json() if not response.ok: raise RuntimeError(result.get('error') or result.get('message') or f'HTTP {response.status_code}') print(result['data']['address'], result['data']['qr_code_url']) ``` Static-wallet creation has no request idempotency contract. Do not retry this POST automatically after a timeout. The asset pair is an example only; discover current values through `GET /v1/coins-and-fee`. --- Source: https://docs.coinssend.com/examples/node-withdrawal.md # Node.js Example: Withdrawal and Webhook Receiver Withdrawals require timestamped HMAC signing and must run on a trusted server. There is no request idempotency key: do not automatically resubmit after a timeout or another unknown outcome. ## Submit a Withdrawal ```javascript const crypto = require('node:crypto') function canonicalJson(payload) { return JSON.stringify(payload) .replace(/\//g, '\\/') .replace(/[\u0080-\uFFFF]/g, character => `\\u${character.charCodeAt(0).toString(16).padStart(4, '0')}` ) } function withdrawalSign(body, timestamp, apiKey) { const payloadBase64 = Buffer.from(body, 'utf8').toString('base64') return crypto .createHmac('sha256', apiKey) .update(`${payloadBase64}.${timestamp}`) .digest('hex') } async function initiateWithdrawal({ merchantId, apiKey, network, coin, amount, toAddress }) { const payload = { network, coin, amount, to_address: toAddress } const body = canonicalJson(payload) const timestamp = Math.floor(Date.now() / 1000).toString() const response = await fetch('https://api.coinssend.com/v1/withdrawals', { method: 'POST', headers: { 'Content-Type': 'application/json', Merchant: merchantId, Timestamp: timestamp, Sign: withdrawalSign(body, timestamp, apiKey) }, body }) const result = await response.json() if (!response.ok) { throw new Error(result.error || result.message || `HTTP ${response.status}`) } return result.data } const withdrawal = await initiateWithdrawal({ merchantId: process.env.COINSSEND_MERCHANT_ID, apiKey: process.env.COINSSEND_API_KEY, network: 'tron', coin: 'usdt', amount: '50.75', toAddress: process.env.WITHDRAWAL_DESTINATION }) console.log(withdrawal.withdrawal_id, withdrawal.status) ``` HTTP `200` means the command was accepted; `data.status` is only the current projected state. Persist `withdrawal_id` and wait for signed status webhooks. Timestamp freshness limits replay but does not make the POST idempotent. ## Verify Withdrawal Webhooks Webhook signing is different from request signing: verify HMAC-SHA256 over the exact raw body bytes before parsing JSON. ```javascript const crypto = require('node:crypto') const express = require('express') const app = express() function validWebhookSignature(rawBody, suppliedHex, apiKey) { if (typeof suppliedHex !== 'string' || !/^[a-f0-9]{64}$/.test(suppliedHex)) { return false } const expected = crypto.createHmac('sha256', apiKey).update(rawBody).digest() const supplied = Buffer.from(suppliedHex, 'hex') return supplied.length === expected.length && crypto.timingSafeEqual(supplied, expected) } app.post('/coinssend/webhook', express.raw({ type: 'application/json' }), async (request, response) => { const rawBody = request.body const signature = request.header('X-Signature') if (!validWebhookSignature(rawBody, signature, process.env.COINSSEND_API_KEY)) { return response.status(401).json({ error: 'Invalid signature' }) } const event = JSON.parse(rawBody.toString('utf8')) const deliveryKey = request.header('X-Idempotency-Key') || `${event.event}:${event.data.withdrawal_id}` // Insert deliveryKey into a table with a unique constraint, enqueue new // events, and acknowledge duplicates without applying side effects twice. await enqueueIfNew(deliveryKey, event) return response.status(204).end() }) ``` Withdrawal deliveries currently use `withdrawal:{withdrawal_id}:{event}` as `X-Idempotency-Key`. The header is not a universal guarantee for other webhook families.