Developer Guide

Payment Gateway Integration

Accept crypto payments on any website by embedding a signed payment link that redirects customers to your gateway and back.

How it works

Your gateway uses HMAC-SHA256 signatures to verify every incoming payment request. The external site signs the link before sending the customer — the gateway checks the signature before allowing payment. After completion, the customer is redirected back with a signed response you can verify.

1
External site generates a signed URL
Your server-side code builds a URL with productId, ref, return_url, nonce, expires — then signs all of it with the shared HMAC secret.
2
Customer clicks the link and lands on the gateway
The gateway immediately verifies the signature server-side. If invalid or expired, the page shows an error and the Pay button is blocked.
3
Customer pays with ETH / USDT / USDC
The URL is cleaned from the browser bar instantly. The customer only sees the product and payment options — no params, no editing possible.
4
Gateway redirects back with result
Success sends tx, ref, status=success to your return_url. Failure sends ref, status=failed. Verify the tx hash on-chain to confirm.

Get your secret key

Contact the gateway operator to receive your HMAC secret key — a long random string shared only between you and the gateway. Keep it server-side only. Never put it in frontend code, git, or environment files that ship to the browser.

The secret key must never appear in any URL, client-side JavaScript, or browser storage. It lives on your server only.

You will also receive:

ItemExampleWhat it is
HMAC Secretsk_gw_a3f9...Your signing key — keep private
Gateway Originhttps://pay.example.comBase URL of the gateway
Product ID42ID of the product registered on the gateway
Ref CodeMYSHOPIdentifier for your site — agreed with gateway operator

Understand the signed URL

A valid payment link looks like this:

https://pay.example.com/pay/42?ref=MYSHOP&return_url=https%3A%2F%2Fmyshop.com%2Fconfirm&nonce=a3f9c12b&expires=1716300000000&sig=7d3e9a...
base URL + product ID
ref — your site identifier
return_url — URL-encoded redirect destination
nonce — random 8-byte hex, prevents link reuse
expires — Unix ms timestamp, 10 min window
sig — HMAC-SHA256 over all other params
The signature covers all params together (sorted alphabetically). Changing even one character anywhere in the URL breaks the signature and the gateway rejects it.

Generate a signed link on your server

This code runs on your server when you render the checkout button. Never run it in the browser.

Node.js / Next.js

const crypto = require("crypto");

const GATEWAY_SECRET = process.env.GATEWAY_HMAC_SECRET; // from your .env
const GATEWAY_ORIGIN = "https://pay.example.com";
const PRODUCT_ID    = "42";
const REF_CODE      = "MYSHOP";

function buildSignedUrl(returnUrl) {
  const params = {
    productId:  PRODUCT_ID,
    ref:        REF_CODE,
    return_url: returnUrl,
    nonce:      crypto.randomBytes(8).toString("hex"),
    expires:    (Date.now() + 10 * 60 * 1000).toString(), // 10 minutes
  };

  // Sign: sort keys alphabetically, join as key=value&key=value, then HMAC
  const message = Object.keys(params)
    .sort()
    .map((k) => `${k}=${params[k]}`)
    .join("&");

  params.sig = crypto
    .createHmac("sha256", GATEWAY_SECRET)
    .update(message)
    .digest("hex");

  const query = new URLSearchParams(params).toString();
  return `${GATEWAY_ORIGIN}/pay/${PRODUCT_ID}?${query}`;
}

// Usage — call when rendering your checkout page:
const payUrl = buildSignedUrl("https://myshop.com/order/confirm");
// Embed payUrl in your checkout button hrefNode.js

PHP

<?php
function buildSignedUrl($returnUrl) {
    $secret    = getenv('GATEWAY_HMAC_SECRET');
    $origin    = 'https://pay.example.com';
    $productId = '42';
    $ref       = 'MYSHOP';

    $params = [
        'productId'  => $productId,
        'ref'        => $ref,
        'return_url' => $returnUrl,
        'nonce'      => bin2hex(random_bytes(8)),
        'expires'    => strval(intval(microtime(true) * 1000) + 600000),
    ];

    // Sort keys alphabetically, build key=value string, sign
    ksort($params);
    $message = implode('&', array_map(
        fn($k, $v) => "$k=$v",
        array_keys($params),
        array_values($params)
    ));
    $params['sig'] = hash_hmac('sha256', $message, $secret);

    return $origin . '/pay/' . $productId . '?' . http_build_query($params);
}

// Usage:
$payUrl = buildSignedUrl('https://myshop.com/order/confirm');
?>
<a href="<?= htmlspecialchars($payUrl) ?>">Pay with Crypto</a>PHP

Python

import hmac, hashlib, os, secrets, time
from urllib.parse import urlencode

def build_signed_url(return_url):
    secret     = os.environ["GATEWAY_HMAC_SECRET"].encode()
    origin     = "https://pay.example.com"
    product_id = "42"

    params = {
        "productId":  product_id,
        "ref":        "MYSHOP",
        "return_url": return_url,
        "nonce":      secrets.token_hex(8),
        "expires":    str(int(time.time() * 1000) + 600_000),
    }

    # Sort keys alphabetically, build message, sign
    message = "&".join(f"{k}={params[k]}" for k in sorted(params))
    params["sig"] = hmac.new(secret, message.encode(), hashlib.sha256).hexdigest()

    return f"{origin}/pay/{product_id}?{urlencode(params)}"

# Usage:
pay_url = build_signed_url("https://myshop.com/order/confirm")
Python

Handle the redirect back

After payment, the customer is redirected to your return_url with these query params appended:

ParamWhen presentValue
statusAlwayssuccess or failed
txOn success onlyThe blockchain transaction hash, e.g. 0xabc123...
refAlwaysYour ref code echoed back, e.g. MYSHOP
Never trust the redirect alone. Always verify the tx hash on-chain or via a block explorer API before marking an order as paid. The redirect can be faked by anyone who guesses the URL structure.

Example confirmation page (Node.js)

// pages/order/confirm.js (Next.js API route or page)
export default function ConfirmPage({ query }) {
  const { status, tx, ref } = query;

  if (status === "success" && tx) {
    // 1. Verify tx on-chain (recommended)
    //    GET https://api.etherscan.io/api?module=transaction&action=gettxreceiptstatus&txhash={tx}
    //    Check status === "1" (success) and to === your contract address

    // 2. Then mark the order as paid in your database
    markOrderPaid({ ref, tx });
  }
}Node.js

Verify with Etherscan API

// Check a transaction was successful on-chain
const res = await fetch(
  `https://api.etherscan.io/api?module=transaction&action=gettxreceiptstatus` +
  `&txhash=${tx}&apikey=${YOUR_ETHERSCAN_KEY}`
);
const { result } = await res.json();

if (result.status === "1") {
  // Transaction succeeded on-chain — safe to fulfil the order
} else {
  // Transaction failed or not found — do not fulfil
}Node.js

What this protects against

AttackHow it's prevented
Forging a payment link manually The sig is a cryptographic HMAC over all params. Without the secret, a valid sig cannot be produced.
Reusing a valid link nonce is random per request. expires enforces a 10-minute window. After that, the link is dead.
Changing any parameter (ref, return_url, productId) All params are included in the signed message. Modifying any one of them breaks the signature.
Timing attacks on signature comparison The gateway uses crypto.timingSafeEqual() — comparison time is constant regardless of input.
Faking a success redirect Always verify the tx hash independently on-chain. The redirect alone is not proof of payment.
Customer tampering with URL after landing Params are read once into locked state and the URL is immediately cleaned from the browser bar.

Integration checklist

Before going live
Secret key stored in server environment variable only (not in code or git)
buildSignedUrl is called server-side, never in the browser
Your return_url is HTTPS
Confirmation page verifies tx on-chain before fulfilling orders
Orders are only marked paid after on-chain verification, not after redirect
Tested with an expired link — gateway should reject it
Tested with a tampered param — gateway should reject it
Once all items are checked, share your ref code and return_url domain with the gateway operator so they can whitelist you. Contact them to receive your secret key.

Signing algorithm — quick reference

How to sign
  1. Collect all params: productId, ref, return_url, nonce, expires
  2. Sort the param names alphabetically
  3. Build a string: expires=...&nonce=...&productId=...&ref=...&return_url=...
  4. Compute HMAC-SHA256(message, secret), hex-encode the result
  5. Append &sig=<hex> to your URL query string
Parameter reference
ParamTypeDescription
productIdstringProduct ID as registered on the gateway
refstringYour site identifier — agreed with gateway operator
return_urlstringFull URL to redirect to after payment (not URL-encoded when signing)
noncestringRandom hex string — generate fresh for every link
expiresstringUnix timestamp in milliseconds — recommend now + 10 minutes
sigstringHMAC-SHA256 hex digest — computed last, over all above params