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
- Create a project and obtain keys.
- Calculate the signature (SHA-256 → Base64).
- Redirect to https://payment.twizzygate.tech/
Base URL and parameters
Base URL:
Copied!
| Parameter | Type | Required | Example | Description |
|---|---|---|---|---|
| orderNum | string | Yes | 456 | Order ID on merchant side |
| amount | number | Yes | 100.00 | Amount (2 decimals or integer) |
| currency | string | Yes | RUB | Currency |
| client | string | Yes | pub_123 | Client public key |
| sign | string | Yes | Base64 | Signature (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¤cy=RUB&orderNum=456&my_secretsign:
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¤cy=RUB&client=pub_123&sign=KhizMnxNLB%2FXuSSoLhLZJz%2F%2Boq4nwLr3UVTeOYNOWlo%3DcURL
Copied!
curl -L
"https://payment.twizzygate.tech/?orderNum=456&amount=100.00¤cy=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
| Parameter | Type | Required | Description |
|---|---|---|---|
| orderNum | string | Yes | Order ID on merchant side |
| amount | string | Yes | Payment amount |
| currency | string | Yes | Currency (e.g. RUB) |
| client | string | Yes | Client public key |
| sign | string | Yes | Signature (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
| Name | Description |
|---|---|
| X-Client-Key | Client public key. |
| X-Signature | Request 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.
- Use the orderNum and status fields from the request body.
- Sort keys alphabetically.
- Build the string key=value with & and append your secret key (without a key name).
- Compute SHA-256 and encode the result in Base64.
- 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.