QryptoPay setup guide
QryptoPay is a self-hosted crypto acquiring solution that allows you to automatically accept cryptocurrency payments on your website. The module is easy to integrate: you can generate payment links on the fly, share them with your customers, and receive funds directly to your own crypto wallets.
The entire solution is deployed on your own server, giving you full control over your funds and a higher level of security.
What you need to get started
QryptoPay is a module of the BeAdmin control panel. To use it properly, you will need:
- a server with the
BeAdminpanel installed (see the installation guide); - a domain name pointing to this server (for example,
qpay.mysite.com).
⚠️ Warning
The domain name is used as the address of the payment page to which your customers will be redirected to complete the payment. This page runs on the same server where QryptoPay is installed. If you don’t have a domain yet, you can add it later at any time.
Step 1. Module installation
To ensure QryptoPay works correctly, you need to install the following modules:
- DOCKER — required, as QryptoPay runs inside Docker containers;
- NGINX — can be installed later, but without it the payment page cannot be launched.
To install each module, open the corresponding section in the sidebar and click Install. Modules must be installed sequentially, one after another.
After all dependencies are installed, return to the QryptoPay module and start the installation of the module itself.
Step 2. Creating a store
Once QryptoPay is successfully installed, create a store. Click the Create button and enter any name you prefer (it can be changed later if needed).
If the NGINX module is already installed and you have a domain for the payment page, you can specify it in the additional settings. In this case, the payment page will be created automatically together with the store.
After the store is created, you can proceed to its configuration.
Each store includes the following components:
- two terminals: primary and test — used to configure and verify the integration with your project;
- Customers section — used to track payers (data depends on the selected terminal);
- Payments section — used to view and analyze payments (data depends on the selected terminal);
- Wallets section — used to configure payment methods and receive funds (available only for the primary terminal).
ℹ️ Info
The test terminal does not require wallets to operate, so wallet creation is not available for it.
For the store to work properly, you need to connect your project to a terminal and add at least one wallet.
Step 3. Integration setup
The key element of the integration is the terminal. Each terminal has the following parameters:
- ID — a unique identifier;
- Token pair — public and private tokens used to identify payments;
- Webhook — the URL of your project where QryptoPay sends payment status notifications;
- Webhook key — used to authorize webhook notifications.
⚠️ Warning
The private token must be stored securely. If it is compromised, attackers may interfere with your payment processing logic. For this reason, after generation QryptoPay does not store the private token on its side.
If the token is lost or compromised, you can always generate a new token pair — just make sure to update the values in your project.
The webhook key is less critical, but it is still recommended to keep it private to prevent forged webhook notifications.
We recommend starting the integration with the test terminal. Unlike the primary terminal, it allows you to verify that the integration works correctly without actually transferring cryptocurrency.
First, generate a token pair for the test terminal and securely store the private token.
In your project settings, save the terminal parameters as environment variables. For example, your .env file may look like this:
TERMINAL_ID: <terminal ID>
PRIVATE_TOKEN: <generated private token>
WEBHOOK_KEY: <webhook key from settings>
PAYMENT_URL: <payment page domain>Next, add a method in your project to generate a signature for payment requests. This function creates a unique signature based on the private token and the terminal ID, which can be safely transmitted without additional encryption.
Example of a signature generation method in pseudocode:
function generate_token(private_key_b64, terminal_uuid) -> string
# Decode the private key from base64/base64url into a raw byte seed
seed := decode_base64_any(private_key_b64)
# Generate a unique nonce (typically uuid4)
nonce := uuid_v4()
# Build the payload (required fields)
payload := {
ts: current_unix_time_seconds(),
nonce: nonce,
terminal_uuid: terminal_uuid
}
# Serialize the payload into a deterministic (canonical) byte representation
payload_bytes := canonical_encode(payload)
# Encode the payload into a transport-safe format
payload_part := base64url_no_padding(payload_bytes)
# Sign the payload representation (in this scheme, payload_part is what gets signed)
signature_bytes := sign(seed, bytes(payload_part, ASCII))
# Encode the signature
signature_part := base64url_no_padding(signature_bytes)
# Final token format: "<payload_b64u>.<sig_b64u>"
return payload_part + "." + signature_part
end⚠️ Warning
To generate public_token, you must provide three required parameters: the current timestamp, a nonce (recommended: uuid4), and the terminal ID.
The timestamp is used to calculate the link’s lifetime (1 hour). The nonce is a security measure that prevents multiple payment intents from being created from the same link (spam protection). Without the terminal ID, QryptoPay cannot process the payment.
Next, you need a method to generate the payment link. The link has the following structure and supports these parameters:
https://qpay.mysite.com/? // your payment page domain
amount_usd=55.1& // amount in USD, format 00.00
payment_mid=...& // transaction ID in your system
public_token=...& // signature generated by your signing method
back_to_store_link=...& // link back to your website after payment (you may add your own params)
customer.id=...& // customer ID in your system (required)
customer.email=...& // customer email in your system (optional, shown in the QryptoPay UI)
metadata.any=any // any extra params you want to pass as metadata.key=value⚠️ Warning
The payment link uses a nonce and can only be used once. If a customer opens the link, selects a currency, and starts the checkout flow (clicks Continue), the payment parameters can no longer be changed. In that case, the customer must return to your website and generate a new payment link. This behavior is intentional and designed for security.
⚠️ Warning
If a customer’s email changes, QryptoPay will match the customer by ID on the next payment and overwrite the email if it differs. The updated email will also be applied to previously created payments.
The overall flow for generating a payment link looks like this:
- create a payment intent in your project (for example, a transaction);
- convert the payable amount to USD (QryptoPay accepts input amounts in USD only);
- build a payment link with the required parameters and send it to the customer.
The customer then completes the payment on your payment page and can return to your store. Your project will receive a success or failure notification with a delay, because blockchain transactions must wait for network confirmations. Typically, this takes 5–10 seconds (Tron, USDT TRC-20) up to 10–30 minutes (Bitcoin).
At this point, you can generate a payment link, open it, select a currency, and proceed with payment. If everything is configured correctly, the payment will appear in your QryptoPay store under the test terminal.
Step 4. Webhook setup
After the customer completes the payment, QryptoPay starts scanning blockchains for the matching transaction. If the payment is made exactly according to the instructions on the payment page, QryptoPay sends a successful payment notification to your webhook.
The notification is sent as an object:
{
"payment_result": payment_result, // payment result: success, mismatch, unexpected
"amount_coins": str(coins), // crypto amount, format 00.00
"amount_fiat": str(amount_usd), // amount in USD, format 00.00
"fiat_code": "USD", // fiat currency code
"coins_asset": "USDC", // asset: BTC, ETH, USDT, USDC, etc.
"coins_chain": "ETH", // network: BTC, ETH, TRX, etc.
"service_id": "qryptopay_pi_uuid", // internal payment ID in QryptoPay
"payment_mid": "string", // transaction ID in your system
"metadata": { "key1": value1, "key2": value2 },
"customer": {
"id": "your_id", // customer ID in your system
"email": "string" // customer email, null if not provided
}
}You can use the received data for post-processing. Some fields are returned exactly as provided—for example, all metadata values passed via the payment link.
⚠️ Warning
If QryptoPay already has the customer’s email saved, but the next payment does not include an email, the webhook payload will contain the email stored in QryptoPay.
On your side, you need a method to verify the signature of incoming webhook notifications. Along with the payload, QryptoPay sends the header TerminalPrivateKey: Token <base64url("uuid:token")>. This header contains the signature token you can use to validate the message.
Example implementation in pseudocode:
function decode_terminal_credentials(header_value) -> (terminal_uuid, webhook_token)
# Extract the base64/base64url part after the "Token " prefix
encoded_part := trim(substring_after(header_value, "Token "))
# Decode base64/base64url into a UTF-8 string
decoded_string := utf8_decode(decode_base64_any(encoded_part))
# Expected format: "uuid:token"
parts := split(decoded_string, ":", limit = 2)
terminal_uuid := parts[0]
webhook_token := parts[1]
return (terminal_uuid, webhook_token)
endThe terminal ID and the webhook key are used to verify payment notifications delivered to your webhook.
Next, on your project side, implement a handler that can process incoming webhook notifications. There are three possible outcomes:
success— the payment was completed successfully. In this case, it’s recommended to compare theamount_fiatvalue from the notification with your internal expected amount before finalizing the payment;mismatch— the customer underpaid or overpaid the expected amount;unexpected— the customer sent funds to the wallet without generating a payment link first.
How you handle each case is up to you. For example, for mismatch you can compare the received amount to the expected amount and, if the payment is short, notify the customer that an additional payment is required. For unexpected, you might choose to credit the customer’s balance automatically and notify them that the payment was received.
After implementing the handler, set the webhook URL in the test terminal settings and repeat the payment flow—generate a new payment link and open it. For the test terminal, the payment is processed immediately and the notification is sent to the webhook right away.
If your project successfully receives the notification, you can consider the test integration complete.
Step 5. Wallet setup
To accept payments, you need to add wallets for each cryptocurrency you plan to support. Wallets are created in external services, and then their details are configured in the QryptoPay module.
A detailed guide on adding and configuring wallets is available at this link.
Step 6. Primary terminal setup
After adding wallets, you can switch your site to use the primary terminal. To do this, follow these steps:
- generate a new token pair for the primary terminal;
- update the terminal settings and specify the webhook URL;
- replace the
ID,PRIVATE_TOKEN, andWEBHOOK_KEYvalues in your project’s.envfile with the credentials from the primary terminal.
After that, you can verify the terminal by creating a test payment. Keep in mind that at this stage you must perform a real cryptocurrency transfer for the wallet you added, so it’s recommended to start with small amounts.
If everything is configured correctly, the payment will be successfully credited in your project.