Overview
The flow is the same for every integration:
- Your server calls
POST /deposit/initiateand gets back a unique Solanaaddressand a hostedpage_url. - You send the customer to
page_url(or show the address / QR yourself). - The customer sends USDT. We confirm it on-chain and call your webhook.
- You mark the order paid. Funds settle to your wallet.
Base URL: https://api.hexpaid.com · All requests and responses are JSON over HTTPS.
Authentication
Sign up, then copy your API key from Dashboard → Developers. Send it in the X-API-Key header from your server — never from browser code.
X-API-Key: merch_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxCreate a payment
POST /merchants/solana/deposit/initiate reserves a Solana address for 10 minutes and starts monitoring it.
| Field | Type | Description |
|---|---|---|
expected_amount | number | Optional. USDT you expect. Enables partial / over-payment detection. Omit to accept any amount. |
order_id | string | Optional. Your order reference, returned in webhooks and visible in the dashboard. |
deposit_id | string | number | Optional. Your own idempotency-style identifier, echoed back as merchant_deposit_id. |
webhook_url | string | Optional. Overrides your account webhook for this payment. Must be https. |
notes | string | Optional. Free text for your records. |
curl -X POST https://api.hexpaid.com/merchants/solana/deposit/initiate \
-H "X-API-Key: $HEXPAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"expected_amount": 25,
"order_id": "ORD-1042"
}'{
"deposit_id": 128,
"status": "pending",
"message": "Deposit initiated. Share the page URL with your customer.",
"page_url": "https://api.swapuzi.com/merchants/solana/deposit/128/page",
"address": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
"order_id": "ORD-1042",
"expires_at": "2026-10-08T12:10:00Z",
"created_at": "2026-10-08T12:00:00Z"
}Payment lifecycle
| status | Meaning |
|---|---|
pending | Address reserved; waiting for the customer’s transfer. |
confirmed | USDT received. With an expected_amount this means the full amount arrived. |
expired | The 10-minute window ended with no (or insufficient) payment. The address is recycled. |
failed | The payment could not be processed. Contact support with the deposit_id. |
When an expected_amount is set, webhooks also carry payment_status: paid (exact), partial (less — keep waiting or top up) or overpaid (more — refund the difference if you wish).
Retrieve & list payments
Poll GET /merchants/solana/deposit/:id as a fallback to webhooks, or list payments with GET /merchants/solana/deposits (filters: status, limit ≤ 200, before_id for paging).
curl https://api.hexpaid.com/merchants/solana/deposit/128 \
-H "X-API-Key: $HEXPAY_API_KEY"Webhooks
Set an account-wide URL in Dashboard → Developers, or pass webhook_url per payment. URLs must be public https endpoints. Return any 2xx within 10 seconds to acknowledge; otherwise we retry after 5 s, 30 s and 2 min (4 attempts total). Every attempt appears in your delivery log.
| Event | Sent when |
|---|---|
deposit.confirmed | The full expected amount (or any amount, if none was set) is received. |
deposit.partial | Less than the expected amount has been received so far. |
deposit.overpaid | More than the expected amount was received. |
deposit.expired | The payment window closed without completion. |
deposit.failed | Processing failed. |
webhook.test | You pressed “Send test event” in the dashboard. |
| Header | Value |
|---|---|
X-HexPay-Event | Event name, e.g. deposit.confirmed |
X-HexPay-Delivery | Unique ID, identical across retries — use it to de-duplicate |
X-HexPay-Timestamp | Unix seconds when the attempt was signed |
X-HexPay-Signature | Hex HMAC-SHA256 of "<timestamp>.<raw body>" with your webhook secret |
{
"event": "deposit.confirmed",
"merchant_id": 42,
"deposit_id": 128,
"order_id": "ORD-1042",
"tx_id": "5VqK…Zk",
"solana_address": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
"amount": 25,
"received_amount": 25,
"expected_amount": 25,
"payment_status": "paid",
"status": "confirmed",
"asset": "USDT",
"timestamp": 1791460800
}Webhooks may arrive more than once or out of order. Make your handler idempotent on deposit_id / X-HexPay-Delivery.
Verify signatures
Always verify the signature against the raw request body, compare in constant time, and reject timestamps older than five minutes to block replays.
import crypto from "node:crypto";
app.post("/webhooks/sonrails", express.raw({ type: "application/json" }), (req, res) => {
const ts = req.get("X-HexPay-Timestamp");
const sig = req.get("X-HexPay-Signature");
const expected = crypto.createHmac("sha256", process.env.HEXPAY_WEBHOOK_SECRET)
.update(ts + "." + req.body).digest("hex");
const valid = sig && sig.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
if (!valid || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return res.sendStatus(401);
const event = JSON.parse(req.body);
if (event.event === "deposit.confirmed") markOrderPaid(event.order_id, event.tx_id);
res.sendStatus(200); // acknowledge fast, do heavy work async
});Settlement
Set the Solana wallet your USDT should land in under Dashboard → Settlement. Changing it needs your password and sends you an email alert.
- Manual withdraw: move your balance to your wallet any time from the dashboard.
- Auto-forward to TRC20: route Solana USDT to your Tron address through Binance.
SOL gas for settlement is sponsored — you never need to hold SOL.
Plugins
Ready-made plugins let you accept crypto on popular e-commerce platforms without writing code. Each one creates a payment for the order, redirects the customer to checkout and marks the order paid when the webhook arrives.
Shopify
Let Shopify customers pay in USDT at checkout, with orders marked paid automatically.
WooCommerce
A payment gateway for WooCommerce and WordPress stores, with automatic order updates.
WordPress
Payment buttons and links you can drop into any page or post.
Wix
Accept crypto on Wix stores and booking sites.
Magento
A checkout extension for Magento and Adobe Commerce.
PrestaShop
A payment module for PrestaShop merchants.
Errors & limits
| HTTP | Meaning |
|---|---|
400 | Invalid body or parameter. The error field explains what to fix. |
401 | Missing or invalid API key / token. |
403 | The endpoint needs a dashboard login (not an API key), or a required password is wrong. |
404 | Payment not found for your account. |
429 | Too many attempts on login / registration endpoints. Wait a minute. |
503 | All deposit addresses are briefly busy. Retry in a few seconds. |
Errors are returned as { "error": "message" }.
Go-live checklist
- Create a payment and send a small USDT amount to see it confirm
- Receive and verify a webhook on your server (use “Send test event”)
- Handle partial, overpaid and expired events
- Store the API key and webhook secret in environment variables, not in source control
- Set your settlement wallet