Max SystemsMAXFIdeveloper hub
DocsConnect a website

Connect a website

A module for an online store (WooCommerce, OpenCart and others) connects to a merchant account without copying keys by hand. The module asks for a connection, the merchant approves it in the merchant portal, and the module receives its own keys once. The flow follows OAuth 2.0 with PKCE (RFC 7636): only the module that started the connection can receive the keys.

How it works

  1. The module creates a random code_verifier (43–128 characters) and sends code_challenge = base64url(SHA-256(code_verifier)) to POST /v2/connect/requests. No key is needed.
  2. The module opens approve_url from the response. The merchant signs in to the merchant portal, sees the website address and approves or rejects the connection.
  3. The module polls GET /v2/connect/requests/{id} every few seconds. When the status is approved, it calls POST /v2/connect/requests/{id}/claim with its code_verifier.
  4. The response contains a test and a live key for a separate payment channel of this website and the webhook secret. They are shown once: save them on the server.
  5. From then on the module works with the regular API (POST /v2/payments/checkout, refunds, webhooks) and sends POST /v2/integrations/heartbeat so the merchant sees that the website is online.

Start a connection

POST /v2/connect/requests — without an API key. Fields:

  • platform — one of: woocommerce, opencart, prestashop, magento2, bitrix, insales, ecwid, tilda, shopify, joomla, drupal, cscart, whmcs, shopware, nopcommerce, wix, squarespace, bigcommerce, custom.
  • site_url — the website address, https only.
  • webhook_url — where MAXFI sends payment events for this website; https and on the same host as site_url.
  • code_challenge — base64url SHA-256 of code_verifier (43 characters); code_challenge_method may be omitted or set to S256.
  • Optional: site_name, plugin_version, cms_version, language (az, ru, en). Unknown fields are rejected.
POST /v2/connect/requests
# code_verifier — random, 43–128 characters, kept on your server
VERIFIER=$(openssl rand -base64 48 | tr '+/' '-_' | tr -d '=\n')
CHALLENGE=$(printf '%s' "$VERIFIER" | openssl dgst -sha256 -binary | openssl base64 | tr '+/' '-_' | tr -d '=\n')

curl https://maxfi-api.exezine.az/v2/connect/requests \
  -H "Content-Type: application/json" \
  -d '{
    "platform": "woocommerce",
    "site_url": "https://shop.example.az",
    "webhook_url": "https://shop.example.az/?wc-api=maxfi",
    "site_name": "Example Shop",
    "plugin_version": "1.0.0",
    "code_challenge": "'"$CHALLENGE"'",
    "code_challenge_method": "S256"
  }'
← 201 Created
{
  "success": true,
  "data": {
    "id": "cr_3f9a1c0b7d2e4a5f6b8c9d0e",
    "status": "pending",
    "approve_url": "https://maxfi-portal.exezine.az/integrations/connect/cr_3f9a1c0b7d2e4a5f6b8c9d0e",
    "expires_at": "2026-11-13T18:30:00.000Z",
    "poll_interval_seconds": 3
  }
}

The request is valid for 30 minutes. Do not put the merchant identifier into the request: the merchant is decided by who approves it in the merchant portal.

Merchant approval

  • The merchant sees the website address, platform, module version and webhook URL, and a warning to approve only their own website that they are connecting themselves.
  • Sessions of MAXFI staff (“sign in as merchant”) are view-only and cannot approve.
  • The merchant can disconnect a website at any time in the merchant portal (Integrations → Connected websites): its keys stop working immediately and its channel is paused.

Receive the keys

POST /v2/connect/requests/{id}/claim with {"code_verifier": "…"}. Keys are issued only once; a second call returns CONNECT_CLAIMED. The response contains:

POST /v2/connect/requests/{id}/claim
curl https://maxfi-api.exezine.az/v2/connect/requests/cr_3f9a1c0b7d2e4a5f6b8c9d0e/claim \
  -H "Content-Type: application/json" \
  -d '{"code_verifier": "'"$VERIFIER"'"}'
← 200 OK
{
  "success": true,
  "data": {
    "site_id": "6c1f2a8e-4b7d-4e2a-9f10-3d5c7b9a1e24",
    "channel_id": "ch_4f1a9c2e7b3d5a60",
    "gateway_url": "https://maxfi-api.exezine.az",
    "live_enabled": false,
    "keys": {
      "test": { "secret_key": "rk_test_…", "key_id": "…" },
      "live": { "secret_key": "rk_live_…", "key_id": "…" }
    },
    "publishable_keys": { "test": "pk_test_…", "live": "pk_live_…" },
    "webhook": {
      "url": "https://shop.example.az/?wc-api=maxfi",
      "secret": "whsec_…",
      "events": ["payment.requires_action", "payment.succeeded", "payment.failed", "payment.refunded", "payment.expired"]
    },
    "permissions": ["payments:write", "refunds:write", "integrations:write"],
    "default_currency": "AZN",
    "allowed_currencies": ["AZN"]
  }
}
  • keys.test.secret_key and keys.live.secret_key — restricted keys rk_test_… and rk_live_… of the website channel with the permissions payments:write, refunds:write, integrations:write. They cannot read payouts, balance or account settings.
  • live_enabled — whether live mode is enabled for the merchant. Until it is, the live key is rejected with 403; use the test key.
  • webhook.secret — the secret for checking X-MAXFI-Signature of events sent to webhook.url (events payment.*).
  • publishable_keys — pk_test_… / pk_live_… for the widget; gateway_url, default_currency, allowed_currencies — for creating payments.

Module heartbeat

POST /v2/integrations/heartbeat with the website key, for example once a day and after each update. All fields are optional:

  • plugin_version, cms_version, runtime_version — up to 40 characters each.
  • last_webhook_at (ISO 8601) and webhook_ok — when the last MAXFI event arrived and whether its signature checked out.
  • problems — up to 20 short messages for the merchant (for example, “currency USD is not enabled for the channel”).
  • If no heartbeat arrives for 36 hours, the merchant portal shows the website as “has not reported for a long time”.
POST /v2/integrations/heartbeat
curl https://maxfi-api.exezine.az/v2/integrations/heartbeat \
  -H "Authorization: Bearer $MAXFI_SITE_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "plugin_version": "1.0.0",
    "cms_version": "WordPress 6.8 / WooCommerce 9.4",
    "runtime_version": "PHP 8.3",
    "last_webhook_at": "2026-11-13T17:58:12Z",
    "webhook_ok": true,
    "problems": []
  }'

Errors

CodeMeaning
VALIDATION_ERROR400 — a required field is missing, a value has the wrong format or there is an unknown field.
INVALID_SITE_URL400 — site_url is not an https address or contains credentials.
INVALID_WEBHOOK_URL400 — webhook_url is not https or does not resolve to a public address.
WEBHOOK_HOST_MISMATCH400 — webhook_url is on a different host than site_url.
TOO_MANY_CONNECT_REQUESTS429 — more than 20 requests from one address in an hour.
CONNECT_PENDING409 — the merchant has not approved yet. Keep polling.
CONNECT_REJECTED409 — the merchant rejected the connection.
CONNECT_EXPIRED409 — 30 minutes have passed or five wrong code_verifier values were sent. Start again.
CONNECT_CLAIMED409 — keys have already been issued for this request.
INVALID_CODE_VERIFIER403 — code_verifier does not match code_challenge.
CONNECT_TEMPORARILY_UNAVAILABLE503 — keys could not be issued; the request stays approved, retry in a minute.
SITE_KEY_REQUIRED403 — heartbeat was sent with a key that does not belong to a connected website.
SITE_NOT_FOUND404 — the website was disconnected; the module must connect again.

Security

  • Keep code_verifier, the secret keys and the webhook secret on the server only. Put only pk_ keys into web pages.
  • Mark an order as paid only after a signed payment.succeeded event or GET /v2/payments/checkout/{id}, not after the customer returns to success_url.
  • If the module is reinstalled, start a new connection and ask the merchant to disconnect the old one.

Shops on InSales, Ecwid and Tilda

These platforms do not run merchant code, so there is no module and no API key on the shop side. The platform sends the customer to MAXFI with a signed or encrypted form; MAXFI checks it, opens the regular MAXFI payment page for exactly the order amount and, once the payment succeeds, tells the platform the order is paid.

  • InSales (external payment method): the merchant connects the shop in the merchant portal (Integrations → CMS) and copies the payment URL, shop ID and password into the InSales admin. The request signature is MD5 with that password.
  • Tilda (universal payment system): the merchant copies the login and secret from the merchant portal. The signature is HMAC-SHA256 with the secret; payment notifications go only to https addresses on Tilda hosts.
  • Ecwid (payment application): the shop is connected from the MAXFI app settings in the Ecwid admin and confirmed in the merchant portal. The checkout form is AES-GCM encrypted with the app secret; the order is marked paid through the Ecwid REST API. The MAXFI app is not published in the Ecwid App Market yet.
  • The amount is taken exactly as the platform sends it. An amount with more decimal places than the currency has is refused, not rounded. The currency must be enabled for the shop channel.
  • A repeated request for the same order opens the same payment page; a changed amount is refused (the customer places the order again).
  • The platform is told “paid” only from the payment status in MAXFI and only when the amount and currency match the order. Returning to a “success” page does not decide anything. Failed notifications are retried (1, 5, 15, 60, 360 minutes, then every 6 hours, up to 20 attempts).
  • Test mode belongs to the connection: a test shop creates test payments (no real money), a live shop requires live mode on the account.
  • Refunds are made in the merchant portal or with POST /v2/payments/{id}/refund. InSales and Tilda have no refund in their protocols — the merchant changes the order status manually.

Addresses the platforms call

  • POST /integrations/insales/{connection_id}/pay — the payment URL given to InSales.
  • POST /integrations/tilda/pay — the payment URL given to Tilda.
  • POST /integrations/ecwid/pay, GET /integrations/ecwid/settings, POST /integrations/ecwid/webhook — the MAXFI Ecwid app.
  • These addresses are for the platforms, not for your code; to accept payments from your own backend use the payment page.
Connect a website — MAXFI Developer Hub · MAXFI Developer Hub