Merchant API
Public Integration Guide
How to connect your main site to this payment proxy: create a payment, send the customer to card checkout, receive webhooks, and handle the return URL. The exchange provider is not exposed to your backend.
API Documentation
Server-to-server integration for merchants (your hidden main site).
Overview
1. Your backend calls POST /api/merchant/payments/init (signed) and gets checkout_url.
2. Redirect the customer to checkout_url on this proxy domain.
3. Customer pays by card (fiat → crypto); they are redirected to the payment partner page.
4. This proxy sends a webhook to your URL when status changes.
5. Customer returns to your return_success_url / return_cancel_url with a signed query (UX only).
Important: mark the order paid and deliver the product only on webhook newStatus: PAID, not on browser redirect alone.
1. Credentials
You receive from the proxy operator:
merchant_api_key— Bearer token for init requestsmerchant_webhook_secret— sign init requests and verify webhooks + return URL- Allowlisted hosts for
return_success_urlandreturn_cancel_url
Do not use SimpleSwap or other provider API keys on your main site.
2. Authentication (init)
Headers
Authorization: Bearer YOUR_MERCHANT_API_KEY Content-Type: application/json X-Timestamp: 1717580000 X-Signature: sha256=<hex>
Signature: HMAC_SHA256(secret, X-Timestamp + "." + raw_json_body), header value sha256=<hex>.
Clock skew tolerance: 300 seconds.
3. Create payment (init)
Request body
{
"merchant_order_id": "order-12345",
"amount_receive": "100.00",
"ticker_to": "usdt",
"network_to": "trc20",
"address_to": "TYourMerchantWallet...",
"extra_id_to": "",
"return_success_url": "https://main-site.example/order/12345/success",
"return_cancel_url": "https://main-site.example/order/12345/cancel",
"merchant_webhook_url": "https://main-site.example/api/webhooks/cryptopay",
"metadata": { "orderId": "12345" },
"customer_email": "user@example.com",
"description": "Order #12345",
"fiat_currency": "usd",
"fixed": false,
"expires_in_seconds": 3600
}amount_receive — how much crypto must arrive on address_to (merchant wallet).
fiat_currency — card payment currency: usd, eur, or gbp (default usd).
metadata — optional object (max 5 KB), echoed back in webhooks unchanged.
provider — optional; default simpleswap.
Response
{
"ok": true,
"payment_id": "550e8400-e29b-41d4-a716-446655440000",
"provider": "simpleswap",
"checkout_url": "https://this-proxy.example/pay/a8f3...64charToken...",
"status": "CREATED",
"expires_at": "2026-06-05T13:00:00Z"
}Checkout redirect: HTTP 302 the customer to checkout_url. They complete card payment on the partner page linked from our checkout.
Deprecated: POST /api/payment/create returns 410 — use init above.
4. Receive webhooks
Configure your endpoint URL in init (merchant_webhook_url) or ask the operator for a default. We POST on status changes.
Idempotency: store X-Webhook-Id and ignore duplicates.
Delivery: respond with 2xx quickly; retries may occur.
Fulfillment: deliver goods only when newStatus is PAID.
Webhook headers
X-Webhook-Signature: sha256=<hex HMAC-SHA256 of raw body> X-Webhook-Timestamp: <milliseconds since epoch> X-Webhook-Id: <unique delivery id>
Webhook body
{
"eventType": "payment.status.changed",
"paymentId": "550e8400-e29b-41d4-a716-446655440000",
"merchantOrderId": "order-12345",
"provider": "simpleswap",
"oldStatus": "AWAITING_DEPOSIT",
"newStatus": "PAID",
"occurredAt": "2026-06-05T12:05:00Z",
"metadata": { "orderId": "12345" },
"providerDetails": {
"providerRef": "ss-exchange-id",
"rawStatus": "finished"
}
}Verify signature (PHP)
function verifyProxyWebhook(string $rawBody, string $signature, string $timestampMs, string $secret): bool
{
$signedPayload = $timestampMs . '.' . $rawBody;
$expected = hash_hmac('sha256', $signedPayload, $secret);
$received = preg_replace('/^sha256=/i', '', $signature);
return hash_equals($expected, $received);
}Verify signature (Node.js)
import crypto from "crypto";
function verifyWebhook(body, signature, timestampMs, secret) {
const signedPayload = `${timestampMs}.${body}`;
const expected = crypto.createHmac("sha256", secret).update(signedPayload, "utf8").digest("hex");
const received = signature.replace(/^sha256=/i, "");
return crypto.timingSafeEqual(Buffer.from(received, "hex"), Buffer.from(expected, "hex"));
}5. Customer return URL
After a terminal status, the user may land on your return_success_url or return_cancel_url with query parameters:
?payment_id=...&merchant_order_id=...&status=PAID&occurred_at=2026-06-05T12:05:00Z&sig=<hmac>
sig = HMAC_SHA256(secret, payment_id=...&merchant_order_id=...&status=...&occurred_at=...). Use for thank-you / error pages only.
6. Payment statuses
7. Errors (init)
401 unauthorized — bad API key or signature
400 validation_error — missing/invalid fields
409 duplicate_order — active payment for same merchant_order_id
503 database_disabled — proxy not fully configured
Need help? Contact support with payment_id and merchant_order_id. Full markdown reference: docs/MERCHANT_API.md in the repository.