API Documentation

API documentation — your quick start to integrating with our payment gateway. Below: payment page link formatting rules, request signing, webhook format, and ready-to-use code examples for Node.js and Python.

Redirect user to payment page

Generate signature and build URL

Base URL and parameters

Base URL:
Copied!
https://payment.twizzygate.tech/
ParameterTypeRequiredExampleDescription
orderNumstringYes456Order ID on merchant side
amountnumberYes100.00Amount (2 decimals or integer)
currencystringYesRUBCurrency
clientstringYespub_123Client public key
signstringYesBase64Signature (see below)
Important!Exclude client and sign from signing. Sort keys, concatenate key=value with & and append secret without a key name.

Request signature

  • Exclude client and sign from the data set.
  • Sort keys alphabetically (ASCII).
  • Build the string: k1=v1&k2=v2&...&secret.
  • Compute SHA-256 of the string and encode in Base64.
Copied!
// ⚠️ Calculate the signature on the server. The secret never goes to the frontend.
import crypto from "node:crypto";

type Params = Record<string, string | number | undefined>;

function makeSign(params: Params, secret: string): string {
  const entries = Object.entries(params)
    .filter(([k, v]) => k !== "client" && k !== "sign" && v !== undefined)
    .sort(([a], [b]) => a.localeCompare(b));

  const base = entries.map(([k, v]) => `${k}=${v}`).join("&") + `&${secret}`;
  const digest = crypto.createHash("sha256").update(base, "utf8").digest();
  return Buffer.from(digest).toString("base64");
}

const params = { orderNum: "456", amount: "100.00", currency: "RUB", client: "pub_123" };
const secret = "my_secret";
const sign = makeSign(params, secret);
console.log(sign);
Test vectors
params: {amount: "100.00", currency: "RUB", orderNum: "456"}, secret: my_secret
base: amount=100.00&currency=RUB&orderNum=456&my_secret
sign: KhizMnxNLB/XuSSoLhLZJz/+oq4nwLr3UVTeOYNOWlo=
Tips: use a stable amount format (e.g. 100.00), do not include empty fields in the signature, store the secret only on the backend.

Examples

Ready URL
Copied!
https://payment.twizzygate.tech/?orderNum=456&amount=100.00&currency=RUB&client=pub_123&sign=KhizMnxNLB%2FXuSSoLhLZJz%2F%2Boq4nwLr3UVTeOYNOWlo%3D
cURL
Copied!
curl -L
  "https://payment.twizzygate.tech/?orderNum=456&amount=100.00&currency=RUB&client=pub_123&sign=KhizMnxNLB%2FXuSSoLhLZJz%2F%2Boq4nwLr3UVTeOYNOWlo%3D"

Errors

If the signature or parameters are incorrect, the user will see an error page.

Embedding payment widget (SDK)

Instead of a redirect, you can embed the payment widget directly on your site. The user stays on your page — the widget opens in a modal window.

Quick start

  • Include the script https://payment.twizzygate.tech/sdk.js on your page.
  • Generate the signature on the server (same as for redirect).
  • Call PayWidget.open({...}) with the order parameters.
Embedding example
Copied!
<!-- Include the script once -->
<script src="https://payment.twizzygate.tech/sdk.js"></script>

<!-- Payment button -->
<button id="pay-btn">Pay</button>

<script>
  document.getElementById("pay-btn").addEventListener("click", function () {
    PayWidget.open({
      orderNum: "456",
      amount: "100.00",
      currency: "RUB",
      client: "your_public_key",
      sign: "request_signature"
    });
  });
</script>

Parameters

ParameterTypeRequiredDescription
orderNumstringYesOrder ID on merchant side
amountstringYesPayment amount
currencystringYesCurrency (e.g. RUB)
clientstringYesClient public key
signstringYesSignature (see section above)

Methods

API
Copied!
// Open widget
PayWidget.open({
  orderNum: "456",
  amount: "100.00",
  currency: "RUB",
  client: "your_public_key",
  sign: "request_signature"
});

// Close widget programmatically
PayWidget.close();

Behavior

  • The widget opens in a modal window over your site.
  • The user can close the modal by clicking the close button or the background.
  • After payment completion (success or cancellation), the "Back to shop" button will automatically close the modal.
  • To close programmatically, use PayWidget.close().
Important: always generate the signature on the server — the secret key must not be exposed in client-side code. Pass the ready signature from backend to frontend.

Operation result callback (Webhook)

After processing the payment, we will send a POST request to your backend with the operation result. You configure the URL in the project settings.

Method
POST
URL

Configured by you (e.g.: https://merchant.com/payment/callback).

Headers
NameDescription
X-Client-KeyClient public key.
X-SignatureRequest signature (same signing rules as for redirect).
Request body (application/json)
{
  "orderNum": "456",
  "status": "success",
  "details": {}
}
  • orderNum — Order ID on your side.
  • status — "success", "fail" or "cancel".
  • details — object with additional information (may be empty).

Callback signature (X-Signature)

Every callback is signed. On your side, you need to recalculate the signature and compare it with the X-Signature header.

  1. Use the orderNum and status fields from the request body.
  2. Sort keys alphabetically.
  3. Build the string key=value with & and append your secret key (without a key name).
  4. Compute SHA-256 and encode the result in Base64.
  5. Compare the result with X-Signature.
Example string (orderNum=456, status=success, secret=sec_abc):
orderNum=456&status=success&sec_abc
Copied!
import crypto from "node:crypto";

function makeCallbackSignature(body: { orderNum?: string; status?: string }, secret: string): string {
  const params = {
    orderNum: body.orderNum,
    status: body.status,
  };

  const entries = Object.entries(params)
    .filter(([, v]) => v !== undefined && v !== null)
    .sort(([a], [b]) => a.localeCompare(b));

  const base = entries.map(([k, v]) => `${k}=${v}`).join("&") + `&${secret}`;
  const digest = crypto.createHash("sha256").update(base, "utf8").digest();
  return Buffer.from(digest).toString("base64");
}

function isValidCallback(body: any, headers: Record<string, string | string[] | undefined>, secret: string): boolean {
  const expected = makeCallbackSignature(body, secret);
  const receivedHeader = headers["x-signature"] ?? headers["X-Signature"];
  const received = Array.isArray(receivedHeader) ? receivedHeader[0] : (receivedHeader || "");

  const expBuf = Buffer.from(expected);
  const recBuf = Buffer.from(received);

  if (expBuf.length !== recBuf.length) {
    return false;
  }

  return crypto.timingSafeEqual(expBuf, recBuf);
}
Important:
  • Store the secret key only on the server.
  • Verify X-Client-Key and its match to your project.
  • Use timing-safe comparison when verifying the signature.