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
- The module creates a random
code_verifier(43–128 characters) and sendscode_challenge= base64url(SHA-256(code_verifier)) toPOST /v2/connect/requests. No key is needed. - The module opens
approve_urlfrom the response. The merchant signs in to the merchant portal, sees the website address and approves or rejects the connection. - The module polls
GET /v2/connect/requests/{id}every few seconds. When the status isapproved, it callsPOST /v2/connect/requests/{id}/claimwith itscode_verifier. - 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.
- From then on the module works with the regular API (
POST /v2/payments/checkout, refunds, webhooks) and sendsPOST /v2/integrations/heartbeatso 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 assite_url.code_challenge— base64url SHA-256 ofcode_verifier(43 characters);code_challenge_methodmay be omitted or set toS256.- Optional:
site_name,plugin_version,cms_version,language(az,ru,en). Unknown fields are rejected.
# 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" }'
{
"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:
curl https://maxfi-api.exezine.az/v2/connect/requests/cr_3f9a1c0b7d2e4a5f6b8c9d0e/claim \ -H "Content-Type: application/json" \ -d '{"code_verifier": "'"$VERIFIER"'"}'
{
"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_keyandkeys.live.secret_key— restricted keysrk_test_…andrk_live_…of the website channel with the permissionspayments: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 checkingX-MAXFI-Signatureof events sent towebhook.url(eventspayment.*).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) andwebhook_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”.
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
| Code | Meaning |
|---|---|
| VALIDATION_ERROR | 400 — a required field is missing, a value has the wrong format or there is an unknown field. |
| INVALID_SITE_URL | 400 — site_url is not an https address or contains credentials. |
| INVALID_WEBHOOK_URL | 400 — webhook_url is not https or does not resolve to a public address. |
| WEBHOOK_HOST_MISMATCH | 400 — webhook_url is on a different host than site_url. |
| TOO_MANY_CONNECT_REQUESTS | 429 — more than 20 requests from one address in an hour. |
| CONNECT_PENDING | 409 — the merchant has not approved yet. Keep polling. |
| CONNECT_REJECTED | 409 — the merchant rejected the connection. |
| CONNECT_EXPIRED | 409 — 30 minutes have passed or five wrong code_verifier values were sent. Start again. |
| CONNECT_CLAIMED | 409 — keys have already been issued for this request. |
| INVALID_CODE_VERIFIER | 403 — code_verifier does not match code_challenge. |
| CONNECT_TEMPORARILY_UNAVAILABLE | 503 — keys could not be issued; the request stays approved, retry in a minute. |
| SITE_KEY_REQUIRED | 403 — heartbeat was sent with a key that does not belong to a connected website. |
| SITE_NOT_FOUND | 404 — 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 onlypk_keys into web pages. - Mark an order as paid only after a signed
payment.succeededevent orGET /v2/payments/checkout/{id}, not after the customer returns tosuccess_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.
