SelSup Developers

SelSup platform · Guides

Extensions and custom integrations

Build a regular HTML/JavaScript page using any framework. SelSup embeds it in an iframe; it does not execute your React code inside the cabinet.

Sections: 11Developer guide
→

Build a regular HTML/JavaScript page using any framework. SelSup embeds it in an iframe; it does not execute your React code inside the cabinet.

Application developers and technology partners

1

Choose the integration type

  1. 1

    External backend service

    Best for ERP, web-store, WMS, BI, and similar synchronization. The token stays on your server and data moves through the API and webhooks.

  2. 2

    Cabinet iframe panel

    Build a regular HTML/JavaScript page using any framework. SelSup embeds it in an iframe; it does not execute your React code inside the cabinet.

  3. 3

    SelSup backend module

    Required for a deep marketplace or delivery integration participating in shared SelSup background processes.

i
Start with a thin vertical slice

Implement one complete workflow and its observability first. Add bulk imports, UI, and more event types after reconciliation is stable.

2

External application architecture

Keep the token in a secrets manager and call SelSup only from your backend. Your browser application should talk to your API and never receive the SelSup token.

Separate the SelSup adapter, job queue, and domain model. API contract changes then remain isolated from the rest of the application.

Use an inbox for incoming events and an outbox for outgoing changes. Both journals should support replay and auditing.

Data flow · Text
SelSup webhook → inbox → domain worker → ERP/WMS
ERP/WMS change → outbox → SelSup API → reconciliation

3

Cabinet iframe panel

Build a regular HTML/JavaScript page using any framework. SelSup embeds it in an iframe; it does not execute your React code inside the cabinet.

Host the panel on a separate HTTPS site that does not share a cookie domain with the cabinet. Open Integrations → My extensions → Add extension and select a name, icon, URL, menu section and position. Save a private panel with no permissions, then enable only the permissions it needs. Store publication requires SelSup review.

4

Server-side integration module

A marketplace integration normally covers tokens, cards, prices, stock, orders, and background status updates. A delivery service adds rating, shipment creation, labels, and tracking.

This module changes the main SelSup backend and follows its review, testing, localization, and release process. Agree on interfaces and data boundaries before implementation.

Convert external API failures into stable ApplicationError values and revoked or invalid external tokens into AccessException according to backend project rules.

i
Shared data compatibility

Do not add marketplace-specific fields to shared entities. Keep raw contracts in integration DTOs and map them at the boundary.

5

Release and operation

  1. 1

    Create a test account

    Separate test tokens, warehouses, products, and orders from customer production data.

  2. 2

    Add observability

    Track queue delay, errors by code, retry counts, and reconciliation discrepancies.

  3. 3

    Plan contract migration

    Support old fields during a transition and deploy consumers before disabling the old format.

  4. 4

    Document disconnection

    Users must know how to revoke a token, remove a webhook, and stop synchronization without losing data.

6

Product and order tabs

Choose a menu page (MENU_PAGE), a product card tab (PRODUCT_TAB), or an order tab (ORDER_TAB). Menu section and position apply only to MENU_PAGE. Tabs require a saved entity with an ID.

7

Context and permissions

Only granted context fields are shared. USER provides user.id and user.locale; CLIENT provides client.id; ORGANIZATION provides accessible organization id and name. PRODUCT separately grants product.modelId on a product card; ORDER grants order.id on an order. Full records, contacts, passwords and tokens are excluded. These are identifiers of the already open page, not proof of access for your backend.

Context v1 · JSON
{
  "type": "selsup.extension.context",
  "version": 1,
  "context": {
    "version": 1,
    "extensionId": "example-extension-id",
    "placement": "MENU_PAGE",
    "user": {"id": 101, "locale": "ru"},
    "client": {"id": 202},
    "organizations": [{"id": 303, "name": "Demo"}]
  }
}
Tab context v1 · JSON
[
  {
    "version": 1,
    "extensionId": "example",
    "placement": "PRODUCT_TAB",
    "apiMethods": [],
    "product": {
      "modelId": 404
    }
  },
  {
    "version": 1,
    "extensionId": "example",
    "placement": "ORDER_TAB",
    "apiMethods": [],
    "order": {
      "id": 505
    }
  }
]

8

postMessage v1 messages

Check the exact event.origin, event.source === window.parent, message type, version and structure. Send ready on connection and request-context to refresh. SelSup rechecks access and coalesces frequent requests. Never pass tokens in URLs, panel localStorage or messages.

Host / extension bridge · JavaScript
const allowedOrigins = new Set(['https://your-cabinet.example.com']);
window.addEventListener('message', (event) => {
  if (event.source !== window.parent || !allowedOrigins.has(event.origin)) return;
  const message = event.data;
  if (!message || message.type !== 'selsup.extension.context' || message.version !== 1) return;
  const context = message.context;
  if (!context || context.version !== 1 || !['MENU_PAGE', 'PRODUCT_TAB', 'ORDER_TAB'].includes(context.placement)) return;
  // The complete sample also validates fields and strips unknown properties.
  document.querySelector('#context').textContent = JSON.stringify(context, null, 2);
});
for (const origin of allowedOrigins) {
  window.parent.postMessage({type: 'selsup.extension.ready', version: 1}, origin);
}
// Refresh after connecting: use the validated origin, never "*".
window.parent.postMessage({type: 'selsup.extension.request-context', version: 1},
  'https://your-cabinet.example.com');

9

API through the iframe bridge

The author declares required methods in the extension settings. Each installing client explicitly grants methods from that declaration. API access defaults to none, including existing installations. Changing the requested list requires another review; installed copies never gain new permissions automatically.

The host calls only the methods below through SelSupFront services with the current employee’s permissions. Existing server access controls still apply. clientService and authService are fully prohibited; all methods outside this list are denied too. Apps cannot supply URLs, headers, tokens or arbitrary Axios configuration. The server checks the current installation and its grants on the actual API request.

API grants are independent of context permissions. context.apiMethods lists granted methods. A grant applies to the method across the cabinet within the employee’s access, rather than only the entity in the current tab. Results contain only the documented fields, never Axios objects or credentials.

One in-flight request and at most one start per second. The last 256 request IDs are protected against duplicates. Host errors: ACCESS_DENIED, INVALID_REQUEST, RATE_LIMITED, DUPLICATE_REQUEST, REQUEST_FAILED. SDK errors also include NOT_CONNECTED, DISCONNECTED and TIMEOUT (20 seconds). Never retry mutations automatically: an error, timeout or iframe closure does not mean the operation was rolled back.

Allowed methods v1 · Text
tagService.findTags              {query?, page?, limit?}   => {rows: [{id, name}]}
tagService.createTag             {name}                   => {id, name}
tagService.updateTag             {id, name}               => {id, name}
tagService.deleteTag             {id}                     => null
productService.updateFavourite   {id, favourite}          => null

id: positive safe integer; updateFavourite.id = product.modelId
favourite: boolean
name: 1..100 characters (trimmed); query: 0..100 characters
page: 1..10000, default 1; limit: 1..100, default 30
Request · JSON
{
  "type": "selsup.extension.api-request",
  "version": 1,
  "requestId": "request-1",
  "method": "tagService.createTag",
  "params": {
    "name": "Demo"
  }
}
Response · JSON
{
  "type": "selsup.extension.api-response",
  "version": 1,
  "requestId": "request-1",
  "ok": true,
  "result": {
    "id": 1,
    "name": "Demo"
  }
}
Sample SDK: after context.apiMethods is received · JavaScript
// connection = connectExtension(...) from selsup-extension.js
// Run from a button click after receiving context.apiMethods.
const tag = await connection.callApi('tagService.createTag', {name: 'Demo'});
console.log(tag.id, tag.name);

10

Sample app and hosting

The sample can be copied into a standalone GitHub repository. With Node.js 22+, npm test and npm start run tests and a local mock cabinet without API keys or real data. Configure config.js and deploy five static files to HTTPS hosting. Download the archive linked below and copy its contents into your GitHub repository.

Local demo · Shell
cd selsup-extension-example
npm test
npm start
# Mock host: http://localhost:4174/
# Panel: http://127.0.0.1:4173/
HTTPS response headers · HTTP
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self'; connect-src 'none'; form-action 'none'; base-uri 'none'; frame-ancestors https://your-cabinet.example.com
Referrer-Policy: no-referrer
X-Content-Type-Options: nosniff
ZIP / GitHub-ready sample · URL
https://api.selsup.ru/assets/examples/selsup-extension-example.zip

11

Security boundaries

The sandbox blocks forms, popups, downloads, top navigation and sensitive browser APIs. The panel origin is shown above the iframe. An extension must never ask for a SelSup password. Its site can still see the visitor IP and retain or forward data granted to it. Review cannot freeze the code served at an external URL. Grant only necessary permissions.

SelSup itself authorizes iframe bridge calls; cookies and tokens never reach the app. Your own backend still needs its own authentication: context does not prove identity. Keep long-lived SelSup tokens on your server only. Full order/product records and inline card blocks are not supported.