Vibe-coding has fundamentally transformed digital product development. Today, adding online card payments, Apple Pay, or Google Pay to your website no longer requires hiring a specialized backend engineer or deciphering complex cryptographic protocols. All you need is the ability to clearly communicate your business logic to an AI agent (Codex, Antigravity, Cursor, or Claude Code) and provide it with the right technical context.
This guide walks you through the complete lifecycle of integrating Monobank Acquiring into your project: from bank compliance requirements and merchant portal setup to generating a production-grade payment gateway with ECDSA SHA-256 webhook signature verification, frontend race condition handling, and automated security tests.
Video Walkthrough: If you prefer visual learning, check out the step-by-step video on YouTube →, demonstrating the entire workflow on a live screen from the initial prompt to real payment settlement.
Agent Skill Package: For maximum code generation accuracy, download the official knowledge package for your AI assistant:
Download full monobank-acquiring.zip archive (38 KB) →
1. Hosted Checkout Architecture and Payment Lifecycle
Monobank Acquiring operates under a Hosted Checkout model (payment processing takes place on the bank's secure page). This eliminates the need for expensive and stringent PCI DSS certification on your server, as customer payment details are entered directly on Monobank's encrypted domain.
The official Monobank documentation for AI tools is available at monobank.ua/api-docs/acquiring/dev/ai-tools/docs--ai-prompts →. Keep this reference handy as the canonical source of bank specifications.
1.1. Core Payment Flow
- 1. Invoice Creation (
invoice/create): The customer selects a product or subscription tier on your site. Your server issues aPOSTrequest to Monobank API with the amount, payment destination, and return URLs. The bank returns a uniqueinvoiceIdand a hostedpageUrl. - 2. Redirect to Payment Page: The customer is redirected to
pageUrl, completing the checkout via Apple Pay, Google Pay, Monobank mobile app, or manual card entry. - 3. Webhook Delivery: Once processed, Monobank sends a
POSTrequest to your pre-configuredwebHookUrlcontaining the transaction status and a cryptographic signature. - 4. Terminal Status Settlement: Your server must handle two terminal states:
success(payment confirmed, grant access or fulfill order) andfailure(payment declined or aborted).
The expired status (checkout link timeout) does not trigger a webhook. If a customer closes the payment page without paying, track invoice abandonment through fallback polling.
2. Legal Requirements and Website Compliance Checklist (FOP / LLC)
Even with flawless code, Monobank's security and financial monitoring departments will decline production terminal activation if your website lacks mandatory legal disclosures required by Ukrainian law and Visa/Mastercard rules.
2.1. Banking Prerequisites
- Active Business Account in Monobank (FOP or Legal Entity): Acquiring cannot be linked to personal consumer cards. You must have an active business account (ФОП or ТОВ) in Monobank.
- Valid Economic Activity Codes (KVED): Your registered business activities must include internet commerce or relevant service codes (e.g.,
47.91— Retail sale via mail order or internet,62.01/62.02— Computer programming and consultancy,85.59— Other education).
2.2. Pre-Moderation Website Checklist
Ensure your website includes the following documents (typically linked in the global footer) before submitting your terminal for review:
- Public Offer Agreement (Terms of Service): Clear description of services or products, contract inception point, user rights, and obligations.
- Privacy Policy: Explicit statements on what user data is collected and how it is secured pursuant to personal data protection laws.
- Refund and Delivery Policy: Clear return and refund terms (14-day statutory return policy under the Consumer Rights Protection Law), or digital service cancellation terms.
- Full Merchant Credentials in Footer: Official entity name (ФОП / ТОВ), Tax ID / USREOU (ІПН / ЄДРПОУ), registered address, customer support phone number, and official e-mail.
- Transparent Pricing: Every payment button must display fixed prices in UAH with an unambiguous description of what the customer is buying.
If your site lacks an offer agreement, business credentials, or displays placeholder prices like "contact for quote," Monobank compliance will reject your acquiring application.
3. Creating a Web Terminal in Monobank Business Cabinet
To interact with the API, you need a merchant authentication key — X-Token. It is generated free of charge inside the web portal.
3.1. Step-by-Step Terminal Setup
- Authorization: Open web.monobank.ua → and log in via QR code using your Monobank mobile app.
- Open Kasa: In the left sidebar navigation, select "Каса" (Cash Desk).
- Add Tool: Click "+ Додати інструмент" (+ Add Tool) and select "Оплати на сайті (власна розробка)" (Website Payments - Custom Integration).
- Register Terminal: Enter a descriptive project name (e.g.,
My WebsiteorProduction Gateway) and confirm by clicking "Підключити" (Connect). - Generate API Token: Open your newly created terminal, switch to the "Інтеграції / API Ключі" (Integrations) tab, click "Створити токен" (Create Token), and copy the secret key.
📍 Portal Navigation:
web.monobank.ua→Каса→+ Додати інструмент→Оплати на сайті (власна розробка)
Creating a payment tool in Monobank Kasa📍 Obtaining API Key:
Каса→Your Terminal→Інтеграція→Створити X-Token
Modal for creating and copying X-Token3.2. Token Security Best Practices
- Never Hardcode in Client-Side Code:
X-Tokengrants direct administrative control over funds and refunds. Never expose it in client HTML/JS or commit it to public GitHub repositories. - Environment Variables Only: Store your token exclusively on your server in
.envasMONOBANK_TOKENor in the Secrets manager of your deployment platform (Vercel, Render, Railway, Replit, Lovable). - Test Token for Staging: For development and sandbox testing, Monobank provides a dedicated mock token at api.monobank.ua →, allowing simulated transactions without moving real funds.
4. Official Monobank AI Prompts for Vibe-Coders
The Monobank team has published official system prompts for AI code generation agents, calibrated to the bank's active API endpoints.
Official Monobank AI Tools and Prompts documentationCommon Beginner Trap — Currency in Minor Units (Kopecks): Monobank API expects all monetary values in minor currency units (kopecks). $100\text{ UAH} = 10,000\text{ kopecks}$. If you pass amount: 100, the customer will be billed only 1 UAH.
4.1. Base Payment Creation Prompt
Copy and paste this prompt into your AI assistant:
4.2. Webhook Handler Setup Prompt
Without a webhook, your server will fail to confirm payments when users close the browser immediately after checkout:
5. The monobank-acquiring Skill Package: Upgrading Your Agent
Relying solely on brief prompts yields basic code (approx. 6.8 out of 10): the button works, but lacks cryptographic signature validation, price tampering safeguards, and network resilience.
To attain production-grade quality (9.8–10 points), inject the specialized monobank-acquiring skill package into your project root.
Payment integration audit comparison before and after using skill5.1. Anatomy of the Skill Package
| Skill File | Contents and Operational Responsibility |
|---|---|
SKILL.md | Master Manifest: Baseline flow, X-Token auth, data schemas, and error codes 400, 403, 429, 500. |
quickstart.md | Quickstart Guide: Step-by-step invoice creation and fallback polling walkthrough with curl snippets. |
invoice.md | Invoice Lifecycle: Endpoints for creating, checking status, cancelling, and invalidating links. |
webhook.md | Cryptographic Security: Exact mathematical ECDSA SHA-256 signature verification for x-sign. |
payment.md | Direct Payments: Token-based recurring charges, synchronous payments, and 3DS verification. |
wallet.md | Card Tokenization (Wallet): Secure storage of payment methods in the bank's vault for 1-click checkout. |
fiscal.md | Receipts & pRRO: basketOrder structure, tax calculation, discounts, and PDF receipt downloads. |
statement.md | Statements & Analytics: Transaction registry query over date ranges with fee accounting. |
merchant.md | Merchant Data: Public key retrieval, submerchant setup, and cashier control. |
examples/ | Ready Servers: Working reference servers in 6 languages (Node.js, Python, Go, PHP, C#, Java). |
6. Practical Implementation: Dynamic Pricing Architecture
Every project is unique: digital consultancies with fixed tiers, e-commerce stores with dynamic shopping carts, or simple donation buttons.
A frequent beginner pitfall is hardcoding payment amounts (e.g. 1000 UAH) directly in client-side code or sending the price in a client POST payload. This creates a severe security vulnerability.
6.1. Security Principle: Dynamic Backend Pricing
-
Never Trust Client-Supplied Amounts: If client-side JavaScript sends
{ price: 1000 }, an attacker can modify DevTools or Postman payloads to{ price: 1 }, purchasing premium items for 1 UAH. -
Server as the Single Source of Truth (SSOT): The frontend sends only a product ID (
productId), tier identifier (planId: "pro"), or cart reference (items: [{ id: "book_1", qty: 2 }]). -
Automatic Conversion to Minor Units: The server resolves the authoritative price from a database or configuration and multiplies it by 100:
$$\text{amount} = \text{Math.round}(\text{realPrice} \times 100)$$
6.2. Universal AI Prompt for Any Codebase
Provide this prompt to your AI assistant (Codex, Antigravity, Cursor, or Claude Code) to inspect your codebase and wire up the payment flow safely:
AI agent analyzing project structure and generating dynamic backend endpoint6.3. Backend Invoice Creation Implementations
typescript// app/api/checkout/create-invoice/route.ts import { NextResponse } from "next/server"; const PRODUCTS_CATALOG: Record<string, { title: string; priceUah: number }> = { plan_starter: { title: "Starter Plan", priceUah: 490 }, plan_pro: { title: "Pro Plan", priceUah: 990 }, plan_vip: { title: "VIP Plan", priceUah: 2490 }, }; export async function POST(req: Request) { try { const { productId } = await req.json(); // 1. Validation: Authoritative price resolved on the server const product = PRODUCTS_CATALOG[productId]; if (!product) { return NextResponse.json({ error: "Selected plan or item not found" }, { status: 400 }); } const amountInKopecks = Math.round(product.priceUah * 100); const orderReference = `order_${productId}_${Date.now()}`; const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || "https://mysite.com"; // 2. Request to Monobank API const response = await fetch("https://api.monobank.ua/api/merchant/invoice/create", { method: "POST", headers: { "X-Token": process.env.MONOBANK_TOKEN!, "Content-Type": "application/json", }, body: JSON.stringify({ amount: amountInKopecks, ccy: 980, // UAH (ISO 4217) merchantPaymInfo: { reference: orderReference, destination: `Payment for: ${product.title}`, comment: `Order ${orderReference}`, }, redirectUrl: `${siteUrl}/payment-result?ref=${orderReference}`, webHookUrl: `${siteUrl}/api/payment/webhook`, validity: 3600, // 1 hour validity }), }); const data = await response.json(); if (!response.ok) { return NextResponse.json({ error: data.errText || "Bank invoice creation failed" }, { status: response.status }); } return NextResponse.json({ checkoutUrl: data.pageUrl, invoiceId: data.invoiceId }); } catch (error) { return NextResponse.json({ error: "Internal payment initialization error" }, { status: 500 }); } }
7. Secure Webhook Handling and ECDSA Cryptographic Signatures
The webhook handler is the most critical component of any financial integration. An attacker could forge a plain HTTP request to /api/payment/webhook with a fake success payload.
Monobank secures webhooks by signing each payload using ECDSA (secp256r1 curve / SHA-256), passed in the HTTP header x-sign.
7.1. Why JSON.stringify Breaks Signature Verification
Critical rawBody Caveat: ECDSA verification requires the exact, unmutated byte stream emitted by Monobank's servers. If you parse JSON and call JSON.stringify(req.body), key ordering, whitespace, or line breaks change. This alters the SHA-256 hash, causing verification to fail every time!
7.2. Production Webhook Verification Implementations
typescript// app/api/payment/webhook/route.ts import { NextResponse } from "next/server"; import crypto from "crypto"; let cachedPubKey: string | null = null; async function getMonobankPubKey(token: string): Promise<string> { if (cachedPubKey) return cachedPubKey; const res = await fetch("https://api.monobank.ua/api/merchant/pubkey", { headers: { "X-Token": token }, next: { revalidate: 86400 }, // Cache public key for 24 hours }); const data = await res.json(); cachedPubKey = `-----BEGIN PUBLIC KEY-----\n${data.key}\n-----END PUBLIC KEY-----`; return cachedPubKey; } export async function POST(req: Request) { const signature = req.headers.get("x-sign"); if (!signature) { return new NextResponse("Missing x-sign header", { status: 400 }); } // 1. Obtain original unparsed payload as text const rawBody = await req.text(); try { const pubKey = await getMonobankPubKey(process.env.MONOBANK_TOKEN!); // 2. Verify ECDSA SHA-256 signature const verifier = crypto.createVerify("SHA256"); verifier.update(rawBody); const isValid = verifier.verify(pubKey, Buffer.from(signature, "base64")); if (!isValid) { console.error("Webhook rejected: Invalid signature"); return new NextResponse("Invalid signature", { status: 400 }); } // 3. Parse JSON only after successful cryptographic verification const payload = JSON.parse(rawBody); const { invoiceId, status, amount, reference } = payload; if (status === "success") { // Fulfill order with idempotency check against duplicates console.log(`Order ${reference} (${invoiceId}) confirmed: ${amount / 100} UAH`); } return new NextResponse("OK", { status: 200 }); } catch (error) { console.error("Webhook error:", error); return new NextResponse("Internal verification error", { status: 500 }); } }
8. Localhost Webhook Testing (Cloudflare Tunnels & ngrok)
When running your app on http://localhost:3000, Monobank cannot deliver webhooks because your machine lacks a public IP.
Monobank requires public endpoints with valid HTTPS. Expose your local port via a secure tunnel during development.
8.1. Instant Tunnel Setup (No Install Required)
bash# Instant public HTTPS tunnel to port 3000 without installing utilities npx untun@latest tunnel --port 3000
This produces an ephemeral public URL like:
https://your-tunnel-name.trycloudflare.com
8.2. Configuring Local Webhooks
Pass your tunnel URL when generating invoices in development:
Test payments will now hit your terminal locally, allowing you to debug x-sign validation in real time.
9. Return Page UX and Race Condition Resolution
When a user completes payment via Apple Pay or the Monobank app, the browser returns to redirectUrl (/payment-result?ref=...) instantly.
However, the bank's background webhook may experience a 1–2 second network delay. If the return page immediately queries your database, it may incorrectly display: "Order not paid," creating user confusion.
9.1. Engineering Pattern for Race Conditions
- Initial Pending State: Open the page with a neutral status:
"Verifying payment with bank..."and an active spinner. - Short Polling: Issue up to 5 quick queries every 1.5 seconds (
/api/orders/check-status?ref=...), waiting for the webhook to flag the order assuccess. - Graceful Fallback: If unconfirmed after 8 seconds, display: "Payment received and processing. Access will unlock automatically within 1–2 minutes."
9.2. Production React Result Component
10. Advanced Features: Embedded pRRO, Hold, and Wallet
Monobank Acquiring natively supports advanced e-commerce mechanics:
Monobank Hosted Checkout page with Apple Pay and card support10.1. Software Fiscalization: Free Embedded Checkbox in Monobank Kasa
Online fiscalization (pRRO) is mandatory for Ukrainian FOP entities in groups 2 and 3.
Monobank provides a free built-in Checkbox integration:
- One-Click Activation: Inside
web.monobank.ua, toggle "Фіскалізація через Checkbox" (Fiscalization via Checkbox) on your terminal. Monobank auto-provisions the cash register and signs receipts with your electronic key. - Zero Code Required for Basic Items: For standard products with uniform tax rates, receipts are generated automatically from the invoice
destinationfield. - Custom Basket via API: For multi-tier tax rates or items requiring customs codes (УКТ ЗЕД), pass
basketOrderinmerchantPaymInfo:
10.2. Two-Stage Pre-Authorization (Hold)
For physical goods subject to warehouse availability checks:
- Reservation: Set
paymentType: "hold"during invoice creation. Funds are held on the customer's card for up to 9 days. - Settlement (Finalize): Call
/api/merchant/invoice/finalizeto capture the full or reduced amount. - Cancellation: If out of stock, call
/api/merchant/invoice/cancelto release the hold without merchant fees.
10.3. Card Tokenization and Subscriptions (Wallet)
For recurring SaaS memberships, pass saveCardData: true during initial checkout. After payment, Monobank returns a walletId via webhook, enabling subsequent one-click or automated recurring charges.
11. Security Test Matrix and Automated Tests (Vitest / Jest)
Payments represent maximum operational liability. Test against this matrix before deploying:
| Security Test | Vector Under Test | Expected System Behavior |
|---|---|---|
| 1. Price Tampering Prevention | Client sends productId: "vip", but injects amount: 100 (1 UAH) | Server discards client amount, resolving true price from config (2490 UAH = 249,000 kopecks). Returns HTTP 400 if ID invalid. |
| 2. Unsigned Webhook Block | POST request hits /api/payment/webhook without x-sign header | Blocked immediately with HTTP 400 Bad Request. No state change in DB. |
| 3. Forged Signature Rejection | Attacker delivers forged signature with status: "success" | crypto.verify(SHA256, ...) fails. Server returns HTTP 400/401. Order remains unpaid. |
| 4. Webhook Idempotency | Monobank delivers duplicate success webhooks due to network lag | Access granted only once. Repeat webhooks return HTTP 200 OK without duplicate fulfillment. |
| 5. Race Condition Resilience | Webhook arrives before invoice creation finishes writing to DB | Handler uses UPSERT or self-heals without throwing 500 errors. |
| 6. Rate Limit Protection | Fallback polling /api/merchant/invoice/status during webhook downtime | Polls spaced at >= 15 seconds to prevent HTTP 429 Too Many Requests. |
11.1. Automated Test Suite (monobank-acquiring.test.ts)
12. Final Pre-Launch Engineering Checklist
Verify your payment module against these 10 items before switching to live payments:
- Site Compliance: Footer contains links to Terms of Service, Privacy Policy, Refund Policy, and legal business credentials with Tax ID.
- Amounts in Kopecks: All
amountfields multiplied by 100 ($1\text{ UAH} = 100\text{ kopecks}$) usingMath.round. - Secret Token Isolation: API key placed in
.envasMONOBANK_TOKENand verified in.gitignore. - Backend Pricing (SSOT): Client sends only product identifiers; amounts are computed strictly on the backend.
- Raw Body Parsing: Webhook verifies unmutated raw text/buffer (
req.text()orreq.rawBody), avoidingJSON.stringifyre-serialization. - Cryptographic Verification: Signature validated against bank public key via ECDSA SHA-256.
- Public HTTPS URL: Webhook endpoint is publicly reachable over valid HTTPS (tested via Cloudflare Tunnel or ngrok).
- Idempotent Storage: Duplicate webhooks do not double-fulfill purchases or issue extra credits.
- Race Condition Handling:
/payment-resultimplements a loading state with short polling. - Real 1 UAH Test Payment: Conducted a successful live test with a real card to confirm bank settlement.