↓ Download .md OpenAPI ↗

API დოკუმენტაცია

Base URL: https://api.quickpay.ge/v1

What you can build

Quickpay is not just a card payment API. A single POST /v1/payments call covers six distinct payment flows — the gateway_slug field determines which flow the customer sees on the hosted payment page.

ℹ For all flows the integration is identical: POST /payments → redirect to payment_url → receive the payment.paid webhook. Only the gateway_slug changes.

💳 Card payment

One-time card charge. Customer enters card details at the bank's secure interface. Visa/MC, Apple Pay, and Google Pay where supported.

bog_card · tbc_card · flitt · unipay · fastoo · moka · keepz

📆 Installment loan

Customer picks a repayment plan (3–36 months). Merchant receives the full amount upfront — the bank handles instalment collection.

bog_installment · tbc_installment · credo_installment · flitt_installment

0️⃣ BNPL (0% financing)

Buy-now-pay-later split into equal 0-interest instalments. The bank absorbs the financing cost — no extra charge to customer or merchant.

bog_bnpl · flitt_bnpl

📱 Open banking / QR / Wallet

Card + QR code + open banking + digital wallet in one gateway. Supports all major Georgian banks via open banking.

keepz · paysera

₿ Crypto

Customer pays in cryptocurrency. CityPay handles the conversion and settles GEL to the merchant. No crypto wallet required on the merchant side.

citypay

🏦 Offline / manual

No hosted payment page. Bank transfer: customer receives your IBAN + order UUID as reference. COD: merchant confirms delivery manually.

bank_transfer · cash_on_delivery

Quick Start

Get your first real payment working in three steps.

1. Get your API key — Dashboard → API Keys → copy your qpk_live_... key. Your key is tied to one Brand in your account.

2. Create a payment — call POST /v1/payments with your amount, description, and return URL. The response contains a payment_url.

curl -X POST https://api.quickpay.ge/v1/payments \
  -H "Authorization: Bearer qpk_live_..." \
  -H "Idempotency-Key: order-1234" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 149.99,
    "currency": "GEL",
    "description": "Order #1234",
    "return_url": "https://yourshop.ge/thank-you"
  }'

3. Redirect & handle webhook — redirect your customer to the payment_url. When payment completes, Quickpay POSTs a payment.paid event to your Webhook URL (Dashboard → Brand Settings). See Webhooks.

Using test mode? Use your qpk_test_... key — no real charges, no real gateway calls. Switch back to your live key for production.

Authentication

All API requests (except exchange rates) require a bearer token. Use your live key (qpk_live_...) for production and your test key (qpk_test_...) for development. Keys are brand-scoped — every key is tied to one Brand in your account.

Authorization: Bearer qpk_live_your_key_here

Rate limit: 100 requests per minute per API key. Rate-limit headers are returned on every response: X-RateLimit-Limit, X-RateLimit-Remaining.

Idempotency

Include an Idempotency-Key header on all POST requests to safely retry without double-charging. The same key returns the same payment response (HTTP 200). If you supply a key that was previously used with a *different amount or currency*, the API returns 409 Conflict.

Idempotency-Key: order-1234-attempt-1

Amounts & Currencies

All monetary values are decimal numbers. 5.00₾ = "amount": 5.00. Supported currencies: GEL · USD · EUR · GBP. When currency is omitted it defaults to GEL.

Errors

Errors always include a message field and a machine-readable error_code string. Validation errors (422) also include an errors object keyed by field name. See Error codes for the full list.

CodeMeaning
401Missing or invalid API key
403Account suspended, test key on live brand, or no active gateway subscription
404Resource not found
409Idempotency key conflict (different amount or currency)
422Validation failed — see the errors object
429Rate limit or charge-frequency exceeded — Retry-After present
500Server or gateway error

Official SDKs

Don't want to call the raw REST API by hand? These are thin, typed clients over this same API — no card data ever touches your server.

SDKPackageInstallRepository
PHP SDKquickpayge/php-sdkcomposer require quickpayge/php-sdkquickpay.ge/php-sdk · GitHub
Laravel Packagequickpayge/laravelcomposer require quickpayge/laravelquickpay.ge/laravel-package · GitHub
Node.js / TypeScript SDK@quickpay/jsnpm install @quickpay/jsquickpay.ge/node-sdk · GitHub

Platform Plugins

Building on WooCommerce, WHMCS, OpenCart, CS-Cart, PrestaShop, or Easy Digital Downloads? Skip the API entirely — install the matching plugin and configure it with an API key.

PlatformTypePage
WooCommerce (WordPress)E-commercequickpay.ge/wordpress-plugin
WHMCSHosting / billingquickpay.ge/whmcs-plugin
OpenCartE-commercequickpay.ge/opencart-plugin
CS-CartE-commercequickpay.ge/cscart-plugin
PrestaShopE-commercequickpay.ge/prestashop-plugin
Easy Digital Downloads (WordPress)Digital goodsquickpay.ge/edd-plugin

Plugins are downloaded from Dashboard → Integrations after registering, and support self-hosted one-click updates.

Create a payment

POST /v1/payments → Try in Playground

Creates a payment and returns a payment_url — redirect your customer there to complete checkout. Omit gateway_slug to let the customer choose their preferred gateway on the hosted payment page.

Body parameters

ParameterTypeDescription
amountnumberrequiredPayment amount as a decimal (e.g. 49.99). Min 0.01, up to 2 decimal places.
currencystringoptionalOne of GEL, USD, EUR, GBP. Defaults to GEL.
descriptionstringoptionalOrder description shown to the customer (max 500 chars).
gateway_slugstringoptionalPre-select a gateway. Omit to show all available gateways. See List gateways and What you can build for valid slugs.
merchant_order_idstringoptionalYour own order reference — stored and returned on all responses (max 255 chars).
bank_transfer_referencestringoptionalCustom reference shown to the customer on the Bank Transfer instructions (e.g. your own invoice number). Only applies to bank_transfer payments — falls back to a Quickpay-generated code when omitted (max 140 chars).
customer_namestringoptionalCustomer full name — pre-fills the checkout form (max 255 chars).
customer_emailstringoptionalCustomer email — pre-fills the form and used for the receipt.
customer_phonestringoptionalCustomer phone — pre-fills the checkout form.
return_urlstringoptionalRedirect URL after checkout completes.
webhook_urlstringoptionalWebhook endpoint for this payment only. Overrides the brand-level webhook URL configured in the dashboard. Useful for multi-tenant setups where each payment routes to a different endpoint.
expires_atdatetimeoptionalISO 8601 datetime after which the payment link expires. Must be in the future.
coupon_codestringoptionalCoupon code to apply — the discount is deducted from amount before charging (max 50 chars). Only applied when gateway_slug is set; silently ignored on gateway-less payments.
product_idintegeroptionalInternal product ID to attach to the payment (used for coupon product-scoping).
product_image_urlstringoptionalProduct image URL shown on the checkout page.
line_itemsarrayoptionalItemised breakdown. The sum of line_items[].total must equal amount.
checkout_template_idintegeroptionalOverride the checkout page template for this payment. Use an id from List checkout templates.
custom_fields_schemaarrayoptionalExtra form fields shown to the customer — see the schema below.
prefill_onlybooleanoptionalWhen true, pre-fill customer info but still show the form for confirmation.
sourcestringoptionalIntegration source tag: woocommerce, whmcs, opencart, cscart, prestashop, edd, or api.
idempotency_keystringoptionalAlternative to the Idempotency-Key header (max 255 chars).

line_items[] object

ParameterTypeDescription
typestringrequiredOne of product, shipping, tax, fee, discount.
namestringrequiredLine item label in English (max 255 chars).
name_kastringoptionalLine item label in Georgian (max 255 chars).
qtyintegerrequiredQuantity (min 1).
unit_pricenumberrequiredPrice per unit.
totalnumberrequiredLine total. The sum of every total must equal amount.
skustringoptionalProduct SKU (max 100 chars).

custom_fields_schema[] object

ParameterTypeDescription
labelstringrequiredField label shown to the customer (max 200 chars).
typestringrequiredOne of text, textarea, select, number, checkbox, radio.
requiredbooleanoptionalWhether the customer must fill this field.
optionsarrayoptionalRequired for select and radio. Each item: { "label": "…", "value": "…" }.

Request example

curl -X POST https://api.quickpay.ge/v1/payments \
  -H "Authorization: Bearer qpk_live_..." \
  -H "Idempotency-Key: order-1234" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 149.99,
    "currency": "GEL",
    "description": "Order #1234",
    "gateway_slug": "bog_card",
    "merchant_order_id": "ORD-1234",
    "customer_name": "Giorgi Beridze",
    "customer_email": "[email protected]",
    "return_url": "https://yourshop.ge/thank-you"
  }'
$response = Http::withToken('qpk_live_...')
    ->withHeaders(['Idempotency-Key' => 'order-1234'])
    ->post('https://api.quickpay.ge/v1/payments', [
        'amount'      => 149.99,
        'currency'    => 'GEL',
        'description' => 'Order #1234',
        'gateway_slug'=> 'bog_card',
        'return_url'  => 'https://yourshop.ge/thank-you',
    ]);
$paymentUrl = $response->json('payment_url');
const res = await fetch('https://api.quickpay.ge/v1/payments', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer qpk_live_...',
    'Idempotency-Key': 'order-1234',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    amount: 149.99, currency: 'GEL', description: 'Order #1234',
    gateway_slug: 'bog_card', return_url: 'https://yourshop.ge/thank-you',
  }),
});
const { payment_url } = await res.json();
import requests
r = requests.post(
    'https://api.quickpay.ge/v1/payments',
    headers={
        'Authorization': 'Bearer qpk_live_...',
        'Idempotency-Key': 'order-1234',
    },
    json={
        'amount': 149.99, 'currency': 'GEL', 'description': 'Order #1234',
        'gateway_slug': 'bog_card', 'return_url': 'https://yourshop.ge/thank-you',
    },
)
payment_url = r.json()['payment_url']
// 201 Created (200 on idempotency replay)
{
  "uuid": "018f2a3b-...",
  "merchant_order_id": "ORD-1234",
  "payment_url": "https://qpy.ge/pay/018f2a3b-...",
  "status": "pending",
  "amount": 149.99,
  "currency": "GEL",
  "discount_gel": null,
  "coupon_code": null,
  "redirect_url": null
}
✓ When gateway_slug is omitted the customer picks the gateway on the hosted page, and the response omits discount_gel, coupon_code, and redirect_url.
✓ Replaying the same Idempotency-Key returns the original payment with HTTP 200; a different amount or currency for the same key returns 409.
bank_transfer_reference is stored even when gateway_slug is omitted — it takes effect once the customer selects Bank Transfer on the hosted page.

Get a payment

GET /v1/payments/{uuid} → Try in Playground

Returns the full payment object.

Path parameters

ParameterTypeDescription
uuidstringrequiredPayment UUID.
// 200 OK
{
  "uuid": "018f2a3b-...",
  "merchant_order_id": "ORD-1234",
  "status": "paid",
  "amount": 149.99,
  "currency": "GEL",
  "description": "Order #1234",
  "customer_name": "Giorgi Beridze",
  "customer_email": "[email protected]",
  "gateway_order_id": "BOG-XYZ789",
  "surcharge": null,
  "coupon": null,
  "form_data": {},
  "line_items": null,
  "refunded_amount": 0,
  "refunds": [],
  "paid_at": "2026-05-16T10:23:00Z",
  "expires_at": null,
  "created_at": "2026-05-16T10:20:00Z"
}

Response fields

FieldTypeDescription
uuidstringUnique payment identifier (UUID v7).
merchant_order_idstring|nullYour order reference passed at creation.
statusstringPayment status — see the list below.
amountnumberCharged amount.
currencystringCurrency code.
descriptionstring|nullOrder description.
customer_namestring|nullCustomer full name.
customer_emailstring|nullCustomer email address.
gateway_order_idstring|nullOrder ID assigned by the payment gateway.
bank_transfer_referencestring|nullReference shown to the customer for Bank Transfer payments — your requested value, or a Quickpay-generated code if none was provided.
surchargenumber|nullGateway surcharge applied, if any.
couponobject|nullApplied coupon details (code, type, discount_gel).
form_dataobjectCustom field values submitted by the customer.
paid_atdatetime|nullISO 8601 timestamp when payment was confirmed.
expires_atdatetime|nullISO 8601 expiry timestamp.
created_atdatetimeISO 8601 creation timestamp.
line_itemsarray|nullItemised breakdown, if provided at creation.
refunded_amountnumberTotal amount refunded so far.
refundsarrayRefund records (uuid, amount, reason, status, initiated_by, created_at).
✓ Payment statuses: pending · paid · failed · refunded · partially_refunded · cancelled · expired · transfer_pending · cod_pending · deposit_paid.

List payments

GET /v1/payments → Try in Playground

Returns a paginated list (20 per page) sorted newest first.

Query parameters

ParameterTypeDescription
statusstringoptionalFilter by status — see the status list above.
gatewaystringoptionalFilter by gateway slug (e.g. bog_card, tbc_card).
fromdateoptionalStart date — YYYY-MM-DD.
todateoptionalEnd date — YYYY-MM-DD. Must be ≥ from.
pageintegeroptionalPage number (default 1).
// 200 OK
{
  "data": [ { "...payment object..." } ],
  "meta": { "current_page": 1, "last_page": 5, "total": 92 }
}

Refund a payment

POST /v1/payments/{uuid}/refund → Try in Playground

Initiates a full or partial refund. Omit amount to refund the full remaining amount. Returns the updated payment object.

Path parameters

ParameterTypeDescription
uuidstringrequiredPayment UUID.

Body parameters

ParameterTypeDescription
amountnumberoptionalAmount to refund. Defaults to the full remaining refundable amount.
reasonstringoptionalInternal reason for the refund — stored in the audit log (max 500 chars).
{ "amount": 50.00, "reason": "Customer requested partial refund" }

Returns 200 OK.

✓ Returns the full payment object with an updated refunds array and refunded_amount.

List products

GET /v1/products → Try in Playground

Returns a paginated list (20 per page) sorted newest first.

Query parameters

ParameterTypeDescription
activebooleanoptionaltrue or false — filter by active status.
pageintegeroptionalPage number (default 1).
// 200 OK
{
  "data": [
    {
      "uuid": "018f...",
      "name_en": "Product Name",
      "name_ka": "პროდუქტის სახელი",
      "short_description_en": "...",
      "short_description_ka": "...",
      "price": 99.99,
      "compare_price": 129.99,
      "base_currency": "GEL",
      "accepted_currencies": ["GEL", "USD"],
      "sku": "SKU-001",
      "track_stock": false,
      "stock_quantity": null,
      "image_url": "https://...",
      "categories": ["clothing"],
      "custom_fields_schema": [],
      "is_active": true,
      "created_at": "2026-01-01T10:00:00Z",
      "updated_at": "2026-01-01T10:00:00Z"
    }
  ],
  "meta": { "current_page": 1, "last_page": 1, "total": 3 }
}

Create a product

POST /v1/products → Try in Playground

At least one of name_en or name_ka is required.

Body parameters

ParameterTypeDescription
name_enstringoptionalProduct name in English (required if name_ka is absent).
name_kastringoptionalProduct name in Georgian (required if name_en is absent).
short_description_enstringoptionalShort description in English.
short_description_kastringoptionalShort description in Georgian.
pricenumberrequiredSale price (min 0.01).
compare_pricenumberoptionalOriginal/crossed-out price shown to highlight a discount.
base_currencystringoptionalBase pricing currency. One of GEL, USD, EUR, GBP. Defaults to GEL.
accepted_currenciesarrayoptionalCurrencies the customer may pay in (e.g. ["GEL","USD"]).
skustringoptionalStock keeping unit identifier (max 100 chars).
track_stockbooleanoptionalEnable stock tracking. Defaults to false.
stock_quantityintegeroptionalCurrent stock level. Only used when track_stock is true.
categoriesarrayoptionalArray of category strings (e.g. ["clothing","sale"]).
custom_fields_schemaarrayoptionalExtra checkout form fields — same structure as the payment custom_fields_schema.
is_activebooleanoptionalWhether the product is available for purchase. Defaults to true.

Returns 201 Created.

✓ Returns the full product object (same fields as Get a product).

Get a product

GET /v1/products/{uuid} → Try in Playground

Returns the full product object.

Path parameters

ParameterTypeDescription
uuidstringrequiredProduct UUID.

Returns 200 OK.

Response fields

FieldTypeDescription
uuidstringUnique product identifier.
name_enstring|nullProduct name in English.
name_kastring|nullProduct name in Georgian.
short_description_enstring|nullShort description in English.
short_description_kastring|nullShort description in Georgian.
pricenumberSale price.
compare_pricenumber|nullOriginal/crossed-out price.
base_currencystringBase pricing currency.
accepted_currenciesarrayCurrencies the customer may pay in.
skustring|nullStock keeping unit.
track_stockbooleanWhether stock tracking is enabled.
stock_quantityinteger|nullCurrent stock level.
image_urlstring|nullPrimary product image URL.
categoriesarrayCategory strings.
custom_fields_schemaarrayExtra checkout fields.
is_activebooleanWhether the product is available.
created_atdatetimeCreation timestamp.
updated_atdatetimeLast update timestamp.

Update a product

PUT /v1/products/{uuid} → Try in Playground

Partial update — include only the fields you want to change. Same field rules as create. Returns the full product object.

Path parameters

ParameterTypeDescription
uuidstringrequiredProduct UUID.

Returns 200 OK.

Delete a product

DELETE /v1/products/{uuid} → Try in Playground

Deletes the product.

Path parameters

ParameterTypeDescription
uuidstringrequiredProduct UUID.
// 200 OK
{ "deleted": true }

POST /v1/checkout-links → Try in Playground

Creates a reusable payment link. Provide either product_uuid (price comes from the product) or a fixed amount.

Body parameters

ParameterTypeDescription
product_uuidstringoptionalAttach an existing product — its price is used automatically. Required if amount is absent.
amountnumberoptionalFixed amount in the chosen currency. Required if product_uuid is absent.
currency_overridestringoptionalOverride the default currency: GEL, USD, EUR, or GBP.
hide_currency_selectorbooleanoptionalHide the currency selector on checkout. Defaults to false.
expires_atdatetimeoptionalISO 8601 datetime after which the link is no longer usable.
max_usesintegeroptionalMaximum number of times the link can be used (min 1).
allowed_gatewaysarrayoptionalRestrict which gateway slugs are shown. Defaults to all active gateways.
custom_fields_schemaarrayoptionalExtra checkout form fields (see note below).
checkout_settingsobjectoptionalCheckout page behaviour overrides (same structure as Brand Checkout Settings).
{
  "amount": 50.00,
  "currency_override": "GEL",
  "expires_at": "2026-12-31T23:59:59Z",
  "max_uses": 100
}
// 201 Created
{
  "uuid": "018f...",
  "url": "https://qpy.ge/p/abc123",
  "slug": "abc123",
  "product_uuid": null,
  "amount": 50.00,
  "currency": "GEL",
  "expires_at": "2026-12-31T23:59:59+00:00",
  "max_uses": 100,
  "is_active": true,
  "created_at": "2026-06-01T09:00:00+00:00"
}
custom_fields_schema uses the same shape as payments except radio is not supported and label is capped at 100 chars. select fields require an options array of { "label": "…", "value": "…" }.

Embed Button

Add a Quickpay checkout button to any HTML page with one script tag. Pass a checkout link UUID in data-checkout-link; the script renders a styled <a> immediately after the script tag, so right-click/open-in-new-tab still works.

<script src="https://qpy.ge/embed.js"
        data-checkout-link="YOUR_CHECKOUT_LINK_UUID"
        data-label="Buy Now"
        data-color="#2563eb">
</script>
AttributeDefaultDescription
data-checkout-linkRequired. Checkout link UUID/slug or a full https:// URL.
data-labelგადახდაButton text.
data-color#2563ebButton background color.
data-text-color#ffffffButton text color.
data-sizemdsm, md, or lg.
data-full-widthfalseSet to true for a block button that stretches to the container width.
data-radius8pxCSS border-radius value.
data-target_selfUse _blank to open checkout in a new tab with rel="noopener noreferrer".
ℹ If your site uses Content Security Policy, allow the Quickpay script host, for example: script-src https://qpy.ge.

List invoices

GET /v1/invoices → Try in Playground

Returns a paginated list (20 per page) sorted newest first.

Query parameters

ParameterTypeDescription
statusstringoptionalOne of draft, sent, viewed, paid, cancelled, overdue.
customer_emailstringoptionalFilter by exact customer email.
fromdateoptionalStart date — YYYY-MM-DD.
todateoptionalEnd date — YYYY-MM-DD. Must be ≥ from.
pageintegeroptionalPage number (default 1).

Returns 200 OK.

Create an invoice

POST /v1/invoices → Try in Playground

Creates an invoice in draft status. Use Send invoice to email it. The response includes a payment_url the customer can use to pay online.

Body parameters

ParameterTypeDescription
customer_namestringrequiredCustomer full name (max 255 chars).
customer_emailstringrequiredCustomer email address.
customer_phonestringoptionalCustomer phone number.
currencystringoptionalOne of GEL, USD, EUR, GBP. Defaults to GEL.
due_atdateoptionalPayment due date — YYYY-MM-DD. Must be after today.
notesstringoptionalNotes shown to the customer on the invoice (max 2000 chars).
termsstringoptionalTerms & conditions shown on the invoice (max 2000 chars).
discount_typestringoptionalpercent or fixed.
discount_valuenumberoptionalDiscount amount. Percent: 0–100. Fixed: deducted from subtotal.
itemsarrayrequiredLine items array — at least one item required.

items[] object

ParameterTypeDescription
descriptionstringrequiredLine item description (max 500 chars).
quantitynumberrequiredQuantity (min 0.01 — supports fractional units).
unit_pricenumberrequiredPrice per unit (min 0).
{
  "customer_name": "Nino Kvaratskhelia",
  "customer_email": "[email protected]",
  "currency": "GEL",
  "due_at": "2026-07-01",
  "discount_type": "percent",
  "discount_value": 10,
  "items": [
    { "description": "Web design — 10 hours", "quantity": 10, "unit_price": 50.00 },
    { "description": "Hosting (monthly)", "quantity": 1, "unit_price": 15.00 }
  ]
}
// 201 Created
{
  "uuid": "018f...",
  "invoice_number": "INV-0001",
  "status": "draft",
  "currency": "GEL",
  "subtotal": 515.00,
  "discount_type": "percent",
  "discount_value": 10,
  "discount_amount": 51.50,
  "total": 463.50,
  "due_at": "2026-07-01",
  "payment_url": "https://qpy.ge/invoice/018f...",
  "items": [
    { "description": "Web design — 10 hours", "quantity": 10, "unit_price": 50.00, "line_total": 500.00 },
    { "description": "Hosting (monthly)", "quantity": 1, "unit_price": 15.00, "line_total": 15.00 }
  ]
}

Get an invoice

GET /v1/invoices/{uuid} → Try in Playground

Returns the full invoice object including payment_url, sent_at, viewed_at, and paid_at timestamps.

Path parameters

ParameterTypeDescription
uuidstringrequiredInvoice UUID.

Returns 200 OK.

Response fields

FieldTypeDescription
uuidstringUnique invoice identifier.
invoice_numberstringHuman-readable invoice number (e.g. INV-0001).
statusstringdraft, sent, viewed, paid, cancelled, overdue.
customer_namestringCustomer full name.
customer_emailstringCustomer email address.
customer_phonestring|nullCustomer phone number.
currencystringCurrency code.
subtotalnumberSum of all line items before discount.
discount_typestring|nullpercent or fixed.
discount_valuenumber|nullDiscount value as entered.
discount_amountnumber|nullActual amount deducted.
totalnumberFinal amount after discount.
notesstring|nullNotes shown to the customer.
termsstring|nullTerms & conditions text.
due_atdate|nullPayment due date.
created_atdatetimeInvoice creation timestamp.
sent_atdatetime|nullWhen the invoice was emailed to the customer.
viewed_atdatetime|nullWhen the customer first opened the invoice.
paid_atdatetime|nullWhen the invoice was paid.
payment_urlstringURL the customer can use to pay online.
itemsarrayLine items (description, quantity, unit_price, line_total).

Send an invoice

POST /v1/invoices/{uuid}/send → Try in Playground

Emails the invoice to the customer and transitions status to sent. Cannot be called when status is paid or cancelled.

Path parameters

ParameterTypeDescription
uuidstringrequiredInvoice UUID.
// 200 OK
{
  "status": "sent",
  "sent_at": "2026-06-01T10:05:00+00:00",
  "email_delivered": true
}
✓ Returns 207 Multi-Status if the status update succeeded but the email failed to deliver — check email_delivered in the response.

List gateways

GET /v1/gateways → Try in Playground

Returns the active gateways for your brand — gateways with an active module subscription. Useful for populating a gateway selector in your own UI. Requires authentication but does not require a gateway license.

// 200 OK
{
  "gateways": [
    {
      "slug": "bog_card",
      "name": "BOG Card",
      "min_amount_gel": 0.5,
      "max_amount_gel": 50000,
      "brand_color": "#FF6600",
      "bg_color": "#FFF3E0",
      "icon_url": "https://cdn.quickpay.ge/gateways/bog.svg",
      "dark_icon_url": "https://cdn.quickpay.ge/gateways/bog-dark.svg"
    }
  ]
}

All supported gateways — static reference. GET /v1/gateways returns only the gateways with an active module subscription on your account.

SlugDisplay nameTypePayment methods
bog_cardBOG CardCardVisa/MC, Apple Pay, Google Pay
bog_installmentBOG InstallmentInstallment loanCard (3–36 month plans)
bog_bnplBOG Part by PartBNPL0% interest split
tbc_cardTBC CardCardVisa/MC, Apple Pay, Google Pay
tbc_installmentTBC InstallmentInstallment loanCard (instalment plans)
credo_installmentCredo InstallmentInstallment loanCard (instalment plans)
flittFlittCardVisa/MC
flitt_installmentFlitt InstallmentInstallment loanCard (instalment plans)
flitt_bnplFlitt BNPLBNPL0% interest split
keepzKeepzMulti-methodCard, QR code, open banking, digital wallet
citypayCityPayCryptoCryptocurrency (settled in GEL)
unipayUniPayCardVisa/MC
fastooFastooCardVisa/MC
mokaMokaCardVisa/MC
paypalPayPalPayPalPayPal balance, card via PayPal
payseraPayseraMulti-methodCard, open banking
bank_transferBank TransferManualIBAN bank transfer (no hosted page)
cash_on_deliveryCash on DeliveryOfflineNo payment collected online

Apple Pay and Google Pay are offered automatically by BOG and TBC on supported devices — no extra integration required.

List checkout templates

GET /v1/checkout-templates → Try in Playground

Returns the active checkout templates for your brand. Use the returned id as checkout_template_id when creating a payment. Requires authentication but does not require a gateway license.

// 200 OK
{
  "templates": [
    {
      "id": 1,
      "uuid": "018f...",
      "name": "Default",
      "is_default": true,
      "gateway_count": 3,
      "fields_count": 2
    }
  ]
}

Exchange rates

GET /v1/exchange-rates → Try in Playground

Public endpoint — no authentication required. Converts an amount between two currencies using live rates.

Query parameters

ParameterTypeDescription
fromstringrequired3-letter source currency code (e.g. GEL).
tostringrequired3-letter target currency code (e.g. USD).
amountnumberrequiredAmount to convert.
// 200 OK
{
  "from": "GEL",
  "to": "USD",
  "rate": 0.3676,
  "amount": 100,
  "converted": 36.76
}

Installments & BNPL

Trigger installment or BNPL checkout with a single POST /v1/payments — no extra API calls needed. Quickpay fetches available plans from the bank and displays them on the hosted payment page automatically.

Merchants receive the full amount upfront. The bank collects instalments from the customer — this is not a deferred payout.

Installment slugs: bog_installment · tbc_installment · credo_installment · flitt_installment — customer picks a repayment plan (3–36 months).

BNPL slugs: bog_bnpl (BOG "Part by Part") · flitt_bnpl — 0% interest split; the bank absorbs the financing cost.

  1. Merchant passes an installment or BNPL gateway_slug in POST /v1/payments.
  2. Customer lands on the hosted payment page — Quickpay fetches available plans live and displays them.
  3. Customer selects a plan and completes checkout at the bank.
  4. Merchant receives the full payment amount upfront.
  5. Webhook fires payment.paid — identical to a card payment.
{
  "amount": 999.00,
  "currency": "GEL",
  "description": "MacBook Pro",
  "gateway_slug": "bog_installment",
  "return_url": "https://yourshop.ge/thank-you"
}

Plan availability and terms are fetched live from each bank's API and cached for 5 minutes. If the bank API is temporarily unavailable, the hosted page shows a graceful "currently unavailable" message.

Recurring Payments

Card-on-file and subscription billing using *_subscriptions gateway slugs. The customer completes the first payment on the hosted page, which charges and tokenises their card with the bank. Subsequent charges happen server-to-server with no customer interaction.

Available recurring slugs: bog_card_subscriptions · tbc_card_subscriptions · flitt_subscriptions · unipay_subscriptions · fastoo_subscriptions · paypal_subscriptions · liberty_card_subscriptions

Step 1   POST /v1/payments { gateway_slug: "bog_card_subscriptions", amount: 29.99 }
         → customer pays on the hosted page
         → payment.paid webhook fires with "subscription_token": "<uuid>"   ← save this

Step 2   POST /v1/payments/charge { subscription_token: "<uuid>", amount: 29.99 }
         → 202 Accepted
         → subscription.charged webhook on success (subscription.failed on failure)

Charge a subscription token

POST /v1/payments/charge → Try in Playground

Charges a stored subscription token server-to-server. The customer completed the first payment on the hosted page using a *_subscriptions slug, which tokenised their card with the bank.

Body parameters

ParameterTypeDescription
subscription_tokenstringrequiredToken returned in the payment.paid webhook after the first payment (UUID).
amountnumberoptionalOverride amount for this charge. Defaults to the original subscription amount.
descriptionstringoptionalDescription for this charge (max 500 chars).
// 202 Accepted
{
  "uuid": "018f...",
  "status": "pending",
  "amount": 29.99,
  "currency": "GEL",
  "subscription_token": "a1b2c3d4-...",
  "message": "Charge initiated. Final result will be delivered via webhook."
}
✓ A minimum of 24 hours must pass between charges on the same token, otherwise the API returns 429 charge_too_frequent with a retry_after timestamp.
Looking for fully managed subscription scheduling? The Subscriptions dashboard module handles billing cycles, dunning, and renewals automatically — no API calls needed.

Webhooks

When a payment status changes, Quickpay sends a POST request to your Webhook URL (Dashboard → Brand Settings). Your endpoint must respond with HTTP 2xx within 30 seconds. Failed deliveries are retried up to 5 times with exponential back-off.

Payment events: payment.paid · payment.failed · payment.refunded · payment.partially_refunded · payment.refund_pending · payment.cancelled · payment.expired

Subscription events: subscription.charged · subscription.failed — fired by POST /v1/payments/charge.

Invoice events: invoice.paid — fired when an invoice is paid.

Lead events: lead.submitted — fired when a landing-page lead is captured.

{
  "event": "payment.paid",
  "uuid": "018f2a3b-...",
  "merchant_order_id": "ORD-1234",
  "status": "paid",
  "amount": 149.99,
  "currency": "GEL",
  "customer_name": "Giorgi Beridze",
  "customer_email": "[email protected]",
  "gateway_order_id": "BOG-XYZ789",
  "subscription_token": "a1b2c3d4-...",
  "paid_at": "2026-05-16T10:23:00Z",
  "created_at": "2026-05-16T10:20:00Z",
  "form_data": {},
  "metadata": {}
}

subscription_token is only present when the payment used a *_subscriptions gateway slug. Save it to use with POST /v1/payments/charge.

Signature verification

Every webhook request includes a QUICKPAY-SIGNATURE header. Always verify it to ensure the request is authentic and hasn't been tampered with.

QUICKPAY-SIGNATURE: t=1716123456,v1=abc123def456...
[$timestamp, $signature] = explode(",", $request->header("QUICKPAY-SIGNATURE"));
$timestamp = substr($timestamp, 2);   // strip "t="
$v1        = substr($signature, 3);   // strip "v1="

// Reject stale payloads (> 5 minutes)
if (abs(time() - (int)$timestamp) > 300) {
    abort(400, "Webhook timestamp too old");
}

$payload  = "{$timestamp}." . $request->getContent();
$expected = hash_hmac("sha256", $payload, $webhookSecret);

if (!hash_equals($expected, $v1)) {
    abort(400, "Invalid signature");
}
const crypto = require('crypto');
const [ts, sig] = req.headers['quickpay-signature'].split(',');
const timestamp = ts.replace('t=', '');
const v1 = sig.replace('v1=', '');

if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) {
    return res.status(400).send('Webhook timestamp too old');
}

const payload = `${timestamp}.${req.rawBody}`;
const expected = crypto.createHmac('sha256', webhookSecret).update(payload).digest('hex');

if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v1))) {
    return res.status(400).send('Invalid signature');
}
import hmac, hashlib, time

header = request.headers.get('QUICKPAY-SIGNATURE', '')
parts = dict(p.split('=', 1) for p in header.split(','))
timestamp, v1 = parts.get('t', ''), parts.get('v1', '')

if abs(time.time() - int(timestamp)) > 300:
    return 'Webhook timestamp too old', 400

payload = f"{timestamp}.{request.data.decode()}"
expected = hmac.new(webhook_secret.encode(), payload.encode(), hashlib.sha256).hexdigest()

if not hmac.compare_digest(expected, v1):
    return 'Invalid signature', 400

Error codes

Every API error response includes a machine-readable error_code string alongside the human message. Switch on these stable codes instead of string-matching translated messages.

{ "error_code": "coupon_expired", "message": "This coupon has expired.", "errors": { "coupon_code": ["This coupon has expired."] } }

The errors object is only present on 422 validation responses.

error_codeHTTPWhen it occursWhat to do
missing_api_key401No Authorization headerAdd an Authorization: Bearer qpk_live_... header
invalid_api_key401Key does not exist or was revokedCheck your API key in Dashboard → API Keys
account_suspended403Merchant account is suspendedContact Quickpay support
test_key_live_mode403Test key used on a live-mode brandUse your live key or enable test mode on the brand
live_key_test_mode403Live key used on a test-mode brandUse your test key or switch the brand to live mode
no_gateway_license403No active module subscription for this gatewaySubscribe to the gateway module in Dashboard → Marketplace
idempotency_conflict409Same Idempotency-Key with a different amount or currencyUse a new key, or keep amount/currency unchanged on retry
not_found404Resource does not existCheck the UUID or slug
validation_failed422Request payload failed validationInspect the errors object and correct the offending fields
rate_limit_exceeded429More than 100 req/min per keyRetry after the Retry-After header interval
gateway_unavailable500Gateway API is temporarily downRetry with exponential back-off
coupon_not_found422Coupon code does not existCheck the coupon code value
coupon_inactive422Coupon is disabledContact merchant support
coupon_not_started422Coupon start date has not arrivedUse the coupon after its start date
coupon_expired422Coupon has expiredUse a different coupon code
coupon_product_mismatch422Coupon is only valid for a different productUse the correct product
coupon_below_min_amount422Order amount is below the coupon minimumIncrease the order amount
coupon_max_uses_reached422Coupon has been fully redeemedCoupon is no longer available
coupon_per_customer_limit422Customer has already used this couponUse a different coupon
coupon_code_taken422A coupon with this code already exists for the brandChoose a different, unused coupon code
line_items_mismatch422Sum of line_items totals ≠ amountFix line items so their totals match the payment amount
template_not_found422checkout_template_id does not existUse a valid ID from GET /v1/checkout-templates
select_options_required422select/radio field missing its options arrayAdd an options array (with label and value keys) to the field schema
amount_or_product_required422Neither amount nor product_uuid was providedInclude one of amount or product_uuid
refund_exceeds_remaining422Refund amount > remaining refundable amountCheck refunded_amount in the payment object
payment_not_refundable422Payment status does not allow refundsOnly paid and partially_refunded payments can be refunded
payment_not_cancellable422Payment status does not allow cancellationOnly pending, initiated, and transfer_pending payments can be cancelled
gateway_refund_unsupported422This gateway does not support API refundsIssue the refund manually via the bank's merchant portal
invoice_not_sendable422Invoice is paid or cancelledInvoices with status draft, sent, viewed, or overdue can be (re)sent
subscription_not_found404subscription_token does not exist or belongs to a different brandCheck the token from the original payment.paid webhook
subscription_inactive422Subscription is cancelled or has expiredOnly active subscriptions can be charged
subscription_already_cancelled422Subscription is already cancelledNo action needed — the subscription is already cancelled
charge_too_frequent429A charge was already initiated within the last 24 hoursRetry after the retry_after timestamp in the response body
webhook_not_configured422No webhook URL is configured for the brandSet a webhook URL with PUT /v1/webhooks before testing or rotating the secret