ruLog in to Senler

Widget Public API and Events

When Public API Is Needed

Use Website Widget for ordinary connection and channel settings. Public API is needed after installation when the site must open chat itself, change context, select a dialog, send a prepared question, or react to widget events.

The Public API does not require a separate cabinet switch. Its methods appear on window.SenlerWidget after the channel code loads. The linked setup guide shows cabinet actions with screenshots; this page covers only actions performed by site code.

Initialization

Create a channel with the screenshot guide, then copy the loader script and channel_id from Embed Code. Call SenlerWidget.init(config) once per page:

<script src="URL_FROM_GENERATED_CODE" crossorigin="anonymous"></script>
<script>
  SenlerWidget.init({
    channel_id: "xxx",
  });
</script>

Replace both placeholders with cabinet values; do not reuse a loader URL from another project's or environment's example. See Initialization Parameters for init, placement modes, visitor data, and theme. Initialization creates one instance. Calling init again destroys the current instance first, so after startup use SenlerWidget methods without recreating the iframe or losing dialog state.

Methods

MethodReturnsResult
SenlerWidget.open(config?)undefinedApplies runtime configuration when supplied and shows the widget.
SenlerWidget.close()undefinedHides the widget while preserving the iframe and dialog state.
SenlerWidget.toggle()undefinedToggles visibility.
SenlerWidget.isOpen()booleanReports whether the widget is visible.
SenlerWidget.selectDialog(dialogId)undefinedSelects an existing dialog without sending a message or automatically showing a hidden widget.
SenlerWidget.setPageContext(items)undefinedReplaces persistent page context.
SenlerWidget.updateRuntime(config)undefinedChanges runtime settings without recreating the widget or saving channel settings.
SenlerWidget.createInlineTextEdit(config)controllerCreates a controller for inline edits.
SenlerWidget.destroy()undefinedRemoves the iframe, button, and handlers. A site-provided embedded container remains in the page.

Use close() rather than destroy() for normal hiding. After close(), call open(); after destroy(), call SenlerWidget.init(...) again.

Before the instance is ready, isOpen() returns false. In button_only mode, open and close methods display nothing; see settings.

After the loader is available, SenlerWidget.runtimeProtocolVersion contains the current protocol number (number). It is a diagnostic compatibility value; do not tie site business logic to a particular number.

Runtime parameters

Runtime parameters are temporary state for the current instance. They are not saved to channel settings and reset after destroy() or another init. open(config?) and updateRuntime(config) accept the same configuration:

ParameterTypeWhat it changes
lang"ru" | "en" | "auto"Interface language.
display_mode"popup" | "embedded"Placement mode. The embedded container must already be provided during init.
theme_mode"light" | "dark" | "auto"Current instance theme.
border_radiusnumberCurrent-instance rounding from 0 to 50.
shellobjectcollapse_button and mobile_edge_swipe.
customActionsobjectComplete set of available site actions and their browser-side handlers.
customActionsLanguage"ru" | "en"Action-description language.
autoExecuteCustomActionNamesstring[]Action names allowed to execute automatically in the current scenario.
dialogIdstringExisting dialog to select.
startNewDialogbooleanCreates a new empty dialog when true.
focusInputbooleanFocuses the message input when true.
pageContextItemsarrayReplaces persistent page context. setPageContext(items) is clearer for navigation.
contextItemsarraySupplies one-time context for the next message.
messageobjectPrepares or automatically sends a message.

When dialogId is supplied, the widget selects that dialog; the ID must be a non-empty string of no more than 200 characters. startNewDialog: true creates an empty dialog, while message.startNewDialog: true sends without the current dialog_id. If dialogId and either form of startNewDialog: true are supplied together, the new dialog wins, so do not combine those intentions in one call. Do not treat autoExecuteCustomActionNames as persistent permission: supply it again in each scenario that needs automatic execution. Matching buttons are hidden; the widget automatically executes only the first matching action in a response.

autoExecuteCustomActionNames accepts no more than 20 unique names. Each name follows the same rules as a custom action name: 1–120 characters, starting with a Latin letter, followed by Latin letters, digits, _, ., :, or -. A duplicate name makes the configuration invalid. List only declared actions: a name without a corresponding customActions entry cannot invoke a site handler.

message accepts { text, requestId?, startNewDialog?, autoSend? }. Text must be a non-empty string up to 10,000 characters. requestId must be a non-empty string up to 200 characters. autoSend: true sends after the widget becomes ready; false or omission only places text in the input. Omit requestId for that draft-only case: result events are intended for automatic sending, and the loader reports a failure if the visitor does not start the request within 10 seconds.

An unknown runtime key, invalid type, or out-of-range value causes a synchronous call error. In production code, pass only fields from this table and catch errors around dynamically assembled configuration.

In an SPA, do not call destroy() and init() again on every transition. Update the page through setPageContext(items) and use open({ contextItems, message }) for a targeted scenario.

Collapsing an embedded widget

Popup mode already has a close button in its header. It hides the window, and the floating button reopens the same instance.

In embedded mode, enable shell.collapse_button: true when the user needs a collapse button in the header. The button only tells the site that the user wants to collapse the widget; the site calls SenlerWidget.close() or closes its outer panel.

SenlerWidget.init({
  channel_id: "xxx",
  display_mode: "embedded",
  container: "#senler-widget",
  shell: {
    collapse_button: true,
  },
  onCollapse(detail) {
    console.log("The user requested collapse", detail);
    SenlerWidget.close();
  },
});

Instead of onCollapse, subscribe once to the event:

window.addEventListener("senler-widget:collapse-request", (event) => {
  if (event.detail.display_mode === "embedded") {
    SenlerWidget.close();
  }
});

detail contains channel_id and display_mode. If both the callback and event listener are configured, both handlers run. Normally choose one to avoid collapsing twice.

CLOSE_WIDGET and COLLAPSE_WIDGET are internal iframe protocol messages. The site must not send them through postMessage.

Mobile gesture

shell.mobile_edge_swipe: true enables a left-edge swipe inside the widget. The loader does not close the interface; it dispatches senler-widget:mobile-edge-swipe.

window.addEventListener("senler-widget:mobile-edge-swipe", (event) => {
  if (event.detail.side === "left") {
    closeMobilePanel();
  }
});

detail contains channel_id, display_mode, and side: "left". This is a navigation signal for the site, not a replacement for the collapse button.

Runtime message result

When message.requestId is provided, the loader dispatches senler-widget:runtime-message-result. It links the site's request to message delivery and response completion without polling history.

const requestId = crypto.randomUUID();

window.addEventListener("senler-widget:runtime-message-result", (event) => {
  if (event.detail.request_id !== requestId) return;

  if (event.detail.status === "message_sent") {
    console.log("Dialog", event.detail.dialog_id);
  }

  if (event.detail.status === "answered") {
    console.log("The answer is ready");
  }

  if (["message_send_failed", "message_answer_failed", "preview_failed"].includes(
    event.detail.status,
  )) {
    console.error(event.detail.error_message);
  }
});

SenlerWidget.open({
  message: {
    text: "Make this text clearer.",
    requestId,
    startNewDialog: true,
    autoSend: true,
  },
});

Possible statuses:

StatusMeaning
acceptedThe automatically sent message configuration was accepted by the widget, but sending has not started yet.
sendingSending started.
message_sentThe message was sent and dialog_id is available.
answeredThe answer completed.
message_send_failedThe message could not be sent.
message_answer_failedAnswer generation failed.
preview_failedA preview could not be built for the associated edit flow.

detail always contains request_id and status; dialog_id and error_message are included where applicable. The loader monitors two separate stages: the iframe has 10 seconds to become ready and accept the request; after delivery, the widget has another 10 seconds to report that sending started. A timeout at either stage produces message_send_failed. Open the dialog with SenlerWidget.open({ dialogId }) or SenlerWidget.selectDialog(dialogId).

For text editing, use the inline controller: it tracks requestId and returns a ready preview.

Credit top-up request

When Offer additional credits is enabled in channel settings, the Add credits button sends an internal message from the iframe to the loader. The loader validates the origin, iframe source, and channel, then dispatches the safe senler-widget:credit-purchase-requested event on window:

const expectedChannelId = "xxx";

window.addEventListener("senler-widget:credit-purchase-requested", (event) => {
  if (event.detail.channel_id !== expectedChannelId) return;

  openCreditPayment({
    channelId: event.detail.channel_id,
    leadId: event.detail.lead_id,
  });
});

event.detail contains the validated channel_id and lead_id. The event only reports the user's intent: it neither processes payment nor changes the credit balance. The website must still verify that channel_id belongs to its integration and then open its own payment form.

Grant credits after payment

After payment is confirmed, the website backend must grant the purchased credits to the lead with a separate server-to-server request:

POST /api/projects/{projectId}/leads/{leadId}/credits
Authorization: Bearer senler_sk_...
Content-Type: application/json

{
  "credits": 50000,
  "type": "purchase",
  "reason": "Payment for order shop-order-123",
  "idempotency_key": "widget-credit-purchase:shop-order-123"
}

Put your integration's projectId in the request path and take leadId from the event. Before opening payment, verify that event.detail.channel_id matches this integration's channel. The project API key must belong to the same project and have the can_manage_leads permission. Keep the key on the backend only: never put it in loader configuration, page JavaScript, or browser network requests.

The credits field accepts an integer number of minimal credit units: one credit displayed to the user equals 10,000 units, so 50,000 grants 5 credits. The price and order contents remain website data and are not sent in this request.

Always reuse one stable idempotency_key for the same paid line item. After a network error, the request can be safely retried with the same body and key; using a new key for the same item grants the credits again. The grant changes only the selected lead's extra balance and does not top up the project's credit balance.

After the grant, the widget receives an update through its existing realtime connection, removes the block, and refreshes the balance. No additional browser request or polling is required.

Callbacks and Events

SenlerWidget.init accepts:

  • contextProvider() — synchronously returns persistent page context; Promises are not supported;
  • onCollapse(detail) — reports a click on shell.collapse_button.

customActions buttons use each action's handler, not a shared callback.

window eventWhen it is dispatched
senler-widget:collapse-requestThe user clicked the collapse button.
senler-widget:mobile-edge-swipeThe user completed an enabled left-edge swipe.
senler-widget:credit-purchase-requestedThe user requested additional credits; detail contains channel_id and lead_id.
senler-widget:runtime-message-resultA runtime message was accepted, completed, or failed.
senler-widget:stageA diagnostic loading, connection, sending, history, or file stage changed. Use it for observability, not business logic.

senler-widget:stage always includes area, phase, and timestamp in detail; it may also include attempt, duration_ms, and a final result: "success" | "error" | "timeout".

areaPossible phase values
loaderinstance-created, iframe-created, iframe-loading, iframe-loaded, app-mounted, ready-timeout, error
bootstraploading, success, error, timeout, interactive
dialogsidle, loading, refetching, success, empty, error
historyidle, loading, refetching, loading-more, success, empty, error
realtimedisabled, token-loading, connecting, connected, reconnecting, disconnected, error
messageoptimistic, sending, queued, sent, failed, waiting, typing, streaming, done, error
uploadrequesting-url, uploading, confirming, ready, error

This is a diagnostic stream, not the final state machine of a business process. The stage set may expand and individual phases may repeat or be skipped, so business logic must not depend on their exact sequence.

Actions in an embedded application

Regular pages only need element markup. For a separate application with its own interaction protocol, pass pageElementActions in init. This is a local website adapter, not an MCP tool; it cannot be changed through updateRuntime.

  • execute(payload) receives event_id, attempt_id, action, target or target_chain, and value for filling or selection. A target contains context_id, an optional role, and an entity_type/entity_id pair for a specific entity.
  • Return null if the target does not belong to the application: the loader will handle it. For your own target, return a result with the same event_id, attempt_id, and action, an executed_at timestamp, and a status of success, not_found, blocked, or failed. Include error_code and a clear error_message for a rejection.
  • A Promise result is supported. The adapter must finish within 2.5 seconds. After an exception, invalid result, or timeout, the loader reports an error and does not retry through another executor. Never discard target constraints: return blocked if the application cannot select a specific entity.
  • clear(scope) clears adapter highlights: tool refers to agent guidance, selected to the user-selected element, and all to both. The loader clears highlights on a new action, guide dismissal, and instance destruction.
SenlerWidget.init({
  channel_id: "YOUR_CHANNEL_ID",
  pageElementActions: {
    async execute(payload) {
      const target = payload.target ?? payload.target_chain.at(-1);
      if (!target?.context_id.startsWith("my-app.")) return null;
      return appBridge.execute(payload);
    },
    clear(scope) {
      appBridge.clearHighlights(scope);
    },
  },
});

The application developer implements appBridge; it is not a Senler method. Its execute must preserve the complete target and return the result format described above. Do not add a second PAGE_ELEMENT_ACTION listener alongside it: the loader already accepts the command and sends its result.

When forwarding an action to child iframes, the loader waits for one frame's response before contacting the next. Only not_found allows the search to continue; success, rejection, or failure ends the attempt. The child-frame search has a total timeout of 2.5 seconds. If the current frame has not replied by then, the result is failed with child_frame_action_timeout, without repeating the action in another frame. A timeout does not prove that the action did not happen: check the application state before retrying.