--- title: "Пример на Node.js" url: "https://docs.x-hub.online/reference/nodejs/" description: "Готовый пример интеграции на Node.js." relatedDocuments: - "https://docs.x-hub.online/reference/errors/" - "https://docs.x-hub.online/reference/python/" - "https://docs.x-hub.online/reference/faq/" --- # Примеры: Node.js Рабочий код на Node.js 18+ с нативным `fetch` и `crypto`. Без зависимостей. ## Клиент X-Hub ```javascript // xhub-client.js import crypto from 'crypto'; const BASE_URL = 'https://api.x-hub.online/api/v1'; export async function createPayment({ amountRub, externalId, description, metadata }) { const res = await fetch(`${BASE_URL}/payments`, { method: 'POST', headers: { 'X-Api-Key': process.env.XHUB_API_KEY, 'X-Api-Secret': process.env.XHUB_API_SECRET, 'Idempotency-Key': crypto.randomUUID(), 'Content-Type': 'application/json', }, body: JSON.stringify({ amount_rub: amountRub, external_id: externalId, payment_method: 'sbp', description, metadata, }), }); if (!res.ok) { const err = await res.json(); throw new Error(`X-Hub ${res.status}: ${err.error?.code}: ${err.error?.message}`); } return res.json(); } export async function getPayment(id) { const res = await fetch(`${BASE_URL}/payments/${id}`, { headers: { 'X-Api-Key': process.env.XHUB_API_KEY, 'X-Api-Secret': process.env.XHUB_API_SECRET, }, }); if (!res.ok) throw new Error(`X-Hub ${res.status}`); return res.json(); } export async function getBalance() { const res = await fetch(`${BASE_URL}/balance`, { headers: { 'X-Api-Key': process.env.XHUB_API_KEY, 'X-Api-Secret': process.env.XHUB_API_SECRET, }, }); return res.json(); } ``` ## Webhook receiver (Express) ```javascript // webhook-server.js import express from 'express'; import crypto from 'crypto'; import { grantAccess } from './business-logic.js'; const app = express(); const processedWebhooks = new Set(); // в проде — Redis/DB app.post( '/webhook/xhub', express.raw({ type: 'application/json', limit: '100kb' }), async (req, res) => { try { const signature = req.headers['x-webhook-signature']; const timestamp = req.headers['x-webhook-timestamp']; // Подпись: HMAC-SHA256(timestamp + "." + body, secret) const signatureData = timestamp + '.' + req.body.toString('utf-8'); const expected = 'sha256=' + crypto .createHmac('sha256', process.env.XHUB_WEBHOOK_SECRET) .update(signatureData) .digest('hex'); if ( !signature || signature.length !== expected.length || !crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected)) ) { return res.status(401).send('Invalid signature'); } const event = JSON.parse(req.body.toString('utf-8')); const dedupeKey = `${event.payment_id || event.withdrawal_id}:${event.status}`; if (processedWebhooks.has(dedupeKey)) { return res.status(200).send('Already processed'); } processedWebhooks.add(dedupeKey); if (event.event === 'payment.status_changed') { if (event.status === 'paid') { await grantAccess(event.external_id, event.metadata); } } res.status(200).send('OK'); } catch (err) { console.error('Webhook error:', err); res.status(500).send('Internal error'); } }, ); app.listen(8080); ``` ---