Max SystemsMAXFIdeveloper hub
DocsWidget SDK

Widget SDK

The Widget SDK (/widget/v1/maxfi.js, version 3.3) opens the MAXFI payment page in a window on your site. The customer picks a payment method and pays inside our frame; card details never reach your page. Your server creates the payment session with your secret key, and your page opens it with your publishable key.

Never put a secret key (sk_live_, sk_test_, rk_, prd_, snd_) into a web page. Anyone can read it from the page source and use your API in your name: read payments with customer data, create payments and make refunds. Since 11 November 2026 the SDK refuses secret keys, and GET /widget/v1/methods rejects them. If a secret key has ever been in your page, replace it in the merchant portal: create a new key and revoke the old one.

Which key goes where

  • Secret key (sk_live_ / sk_test_) — only on your server: POST /widget/v1/session or POST /v2/payments/checkout.
  • Publishable key (pk_live_ / pk_test_) — in the page: new Maxfi('pk_…'). It can only read your payment methods and open a session your server created.
  • The key mode follows the prefix. A live publishable key works once your account is in live mode.

Both keys are in merchant portal → Developer Hub → API Keys. The payment page opens in a frame only on sites listed in merchant portal → Form Builder → Sites allowed to embed.

1. Create the session on your server

POST /widget/v1/session takes amount in minor units and currency (both required), and optionally description, order_id, success_url, cancel_url, language, initial_method, and metadata. language and initial_method work as in POST /v2/payments/checkout, and the payment page shows your merchant name and branding. It answers 401 PUBLISHABLE_KEY_NOT_ALLOWED to a publishable key, and its responses carry no CORS headers, so browsers cannot read them from any site. A session from POST /v2/payments/checkout works too.

# Server-to-server only. Never call this from a browser.
curl -X POST https://maxfi-api.exezine.az/widget/v1/session \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $MAXFI_SECRET_KEY" \
  -d '{
    "amount": 500,
    "currency": "RUB",
    "description": "Order #123",
    "order_id": "order-123",
    "success_url": "https://shop.example.com/success",
    "cancel_url": "https://shop.example.com/cancel"
  }'

# Response
{
  "success": true,
  "data": {
    "session_id": "cs_w_9f2c…",
    "checkout_url": "https://maxfi-api.exezine.az/checkout/cs_w_9f2c…",
    "initial_method": null,
    "expires_at": "2026-11-11T12:30:00.000Z",
    "amount": 500,
    "currency": "RUB",
    "status": "pending",
    "livemode": true
  }
}

Errors come as {"success": false, "error": {"code": "…", "message": "…", "request_id": "…"}}: 400 VALIDATION_ERROR (for example, no amount or currency), 400 CHANNEL_NOT_CONFIGURED (no payment channel on your account), 400 CURRENCY_NOT_ALLOWED (the currency is not enabled for the channel), 409 LIVE_PAYMENTS_NOT_CONFIGURED (a live key, but live payments are not set up yet), 500 INTERNAL_ERROR. A missing or invalid secret key gets 401 UNAUTHORIZED in the general API error format.

In the widget window the payment page opens on the method the customer picked in the window. It picks its language itself, as when the customer opens checkout_url directly: the session language if you passed it, otherwise the customer’s browser language, otherwise your default language from Form Builder. The locale option of the SDK sets the language of both the window and the page and takes precedence over the session language. If you fix the language in the session, pass the same locale to the SDK so that the window matches the page.

2. Open it in the page

<script src="https://maxfi-api.exezine.az/widget/v1/maxfi.js"></script>
<button id="pay-btn">Pay 5.00 RUB</button>
<script>
  // Publishable key only (pk_live_… / pk_test_…).
  var maxfi = new Maxfi('pk_live_…');

  document.getElementById('pay-btn').addEventListener('click', function () {
    maxfi.openCheckout({
      // Ask YOUR server for a session (see step 1)
      fetchSession: function () {
        return fetch('/create-maxfi-session', { method: 'POST' })
          .then(function (r) { return r.json(); });
      },
      amount: 500,           // window header only; the session sets the real amount
      currency: 'RUB',       // header + only the methods available in this currency
      // locale: 'ru',       // optional: force the window and page language (en, ru, az)
      // Payment result confirmed by MAXFI: { maxfi, status, session_id }.
      // Show a thank-you page here; fulfil the order by the webhook.
      onSuccess: function (result) { location.href = '/thank-you'; },
      onError: function (err) { console.error(err); }
    });
  });
</script>

Without writing your own JavaScript, let your server print the session id into the page:

<!-- Your server created the session and prints its id -->
<div data-maxfi-checkout
     data-maxfi-key="pk_live_…"
     data-session-id="cs_w_9f2c…"
     data-amount="500" data-currency="RUB"
     data-label="Pay now"></div>
<script src="https://maxfi-api.exezine.az/widget/v1/maxfi.js"></script>

openCheckout options

sessionIdA session id created by your server. Use it when the page already knows it.
fetchSessionA function that returns a Promise with your server response: { session_id } or the MAXFI response itself. Called when the customer presses the pay button.
sessionUrlAn address on your site. The SDK sends a POST request with JSON from sessionParams (with your site’s cookies when the address is on the same origin) and expects { session_id }.
currencyThe payment currency (ISO 4217), for example AZN. The window offers only the payment methods available in it (SBP, SberPay, T-Pay and YooMoney only for RUB) and formats the amount in the header. If you pass the MAXFI session response as sessionId, its currency is used. Without a currency the methods are not filtered by currency.
localeThe language of the window and the payment page: en, ru or az (language works too). Without it the window uses the customer’s browser language, then the lang attribute of your <html>, then English, and the payment page picks its language itself. For all windows: new Maxfi('pk_…', { locale: 'az' }); in HTML: data-locale.
amount, descriptionShown only in the window header. The amount charged is the one your server put into the session.
onSuccess, onErrorCalled with the payment result confirmed by MAXFI: { maxfi: true, status: 'succeeded' | 'failed', session_id } (see Payment result). onError with { error } — the window could not start the payment. The same events are available via maxfi.on("success" | "error" | "close", fn).

createCheckout(opts) and createButton(elementId, opts) keep their names and take the same options.

Payment result

When the payment ends, the payment page in the window reports the result that MAXFI has confirmed on the server, and the SDK calls onSuccess(result) or onError(result) with { maxfi: true, status: 'succeeded' | 'failed', session_id } (the same object comes to maxfi.on("success" | "error", fn)). After a success the window closes in 2 seconds, after a decline in 4 seconds, so that the customer can read the reason on the payment page. A declined session cannot be paid again: create a new session for another attempt. onError with { error } means something else: the window could not start the payment, for example your server returned no session. This is how maxfi.js 3.2 works (14 November 2026); before, onSuccess was never called.

Fulfil orders only after your server confirms the payment — by the signed webhook payment.succeeded or GET /v2/payments/checkout/{id}. Use onSuccess for the screen, for example to show a thank-you page: the result does not reach your page if the customer closes the window or the tab first, and anything in the customer’s browser can be changed by the customer.

After a decline the result also carries error_code — the decline code from the MAXFI list (for example insufficient_funds or user_cancelled), the same as in GET /v2/payments/checkout/{id} and the payment.failed webhook. Use it to tell the customer what to do, for example to try another card. The result never carries the error text. If the code is unknown, there is no error_code field. Since maxfi.js 3.3.

Sometimes the window closes without a result: the customer closes it while the bank has not answered yet, or the payment page shows an error that the server has not confirmed (for example, a timeout). Then only maxfi.on("close", fn) fires. In this handler, if onSuccess and onError were not called, ask your server for the payment status (GET /v2/payments/checkout/{id}) and wait for the webhook: the payment may still succeed.

The payment page reports the result only to a site listed in merchant portal → Form Builder → Sites allowed to embed, and only to the exact address of the window that embedded it. If you embed the payment page in your own iframe without the SDK, listen for the same message: check that it comes from https://maxfi-api.exezine.az and from your frame, that type is 'maxfi.checkout.result' and that session_id is your session.

<!-- checkout_url of the session your server created -->
<iframe id="maxfi-frame" src="https://maxfi-api.exezine.az/checkout/cs_…" allow="payment"></iframe>
<script>
  var frame = document.getElementById('maxfi-frame');
  window.addEventListener('message', function (e) {
    if (e.origin !== 'https://maxfi-api.exezine.az' || e.source !== frame.contentWindow) return;
    var d = e.data;
    if (!d || d.type !== 'maxfi.checkout.result' || d.session_id !== 'cs_…') return;
    if (d.status === 'succeeded') location.href = '/thank-you'; // fulfil the order by the webhook
    else showPaymentFailed();                                   // a new session for another attempt
  });
</script>

GET /widget/v1/methods

The only endpoint that accepts a publishable key: GET /widget/v1/methods?key=pk_live_…. It returns the payment methods of your account in the key mode and can be read from any site. The saved card (recurrent) is never in the list: it is charged only with POST /v2/payments. The SDK calls it for you and passes the payment currency when it knows it — from the currency option or from the session response (for example &currency=AZN). With a currency the list contains only the methods available in it: SBP, SberPay, T-Pay and YooMoney only for RUB; without it the list is not filtered by currency. If the list cannot be loaded, the response is {"success": true, "data": [], "total": 0, "default_code": null, "unavailable": true}, and the window shows no method tabs: the customer picks the method on the payment page. Errors: 401 for a missing or unknown key, 403 SECRET_KEY_NOT_ALLOWED for a secret key, 403 LIVE_MODE_NOT_ENABLED for a live key before your account is in live mode.

Migrating from v2

  1. Replace the secret key that was in your page: create a new key and revoke the old one (merchant portal → Developer Hub → API Keys).
  2. Add a server endpoint that creates the session (step 1).
  3. Replace new Maxfi('sk_…') with new Maxfi('pk_…') and pass fetchSession, sessionUrl or sessionId to createCheckout / openCheckout.
  4. For data-maxfi-checkout elements: publishable key in data-maxfi-key plus data-session-id or data-session-url.

With a secret key, the v3 SDK logs an error to the console and does not show the button, so the old snippet stops working until it is migrated.

Widget SDK — the payment page on your site · MAXFI Developer Hub