Документація Payments
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
Section titled “Submit a Withdrawal”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
Section titled “Verify Withdrawal Webhooks”Webhook signing is different from request signing: verify HMAC-SHA256 over the exact raw body bytes before parsing JSON.
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.