Landing JavaScript SDK
window.SenlerLanding is available in HTML blocks on published landings. The SDK loads automatically before your scripts; the API version is 1. No separate package is required.
Choose a segment
The button needs to know which segment the visitor should subscribe to. Copy that segment's ID:
- Open landing settings → “Permissions”.
- Add the segment under “Permissions for HTML blocks”.
- Click the copy icon next to the ID below the segment name.
- Paste the copied value into the code example instead of
SEGMENT_ID.
See HTML form settings for selecting segments and permitted fields.
For a basic subscription, only segmentId is required. The other identifiers are needed only for specific scenarios:
| Parameter | When it is needed |
|---|---|
segmentId | Required for subscribe and unsubscribe: identifies the segment to subscribe to or unsubscribe from |
channelId | Optional for subscribe and unsubscribe: provide it when the button must use a specific project channel |
landingId | Required only for openLanding: identifies another landing in the same project to navigate to. Do not pass it to subscribe |
Pass the internal segment ID as segmentId. The short ID from a direct subscription link is not suitable. The segment must belong to the landing project and allow public subscriptions. Allow the segment in HTML form settings and publish the changes. Subscriptions to agent segments must also be published. A standard button on the page is not required.
Without channelId, the SDK uses a matching channel from the current launch or offers the visitor a choice. An explicit channelId takes priority over the page channel. If the visitor is not yet verified in that channel, the corresponding app opens; identity from another channel is not reused to subscribe.
To collect contacts, contactCapture: { phone: true, email: true } requests permitted contact details. Pass custom field values through contact and variables: form example. Required segment consents remain mandatory regardless of these parameters.
The current visitor subscribes through a verified platform context. You cannot pass another user's ID or accept consents through the SDK. When copying HTML to another project, replace segment, channel and landing IDs.
Add your button
On the HTML tab, add a button and a place for the result:
<section>
<button type="button">Subscribe</button>
<p role="status" aria-live="polite"></p>
</section>
On the JS tab, paste the code without <script> tags and replace SEGMENT_ID:
{
const root = document.currentScript.parentElement;
const button = root.querySelector('button');
const message = root.querySelector('[role="status"]');
const sdk = window.SenlerLanding;
const segmentId = 'SEGMENT_ID';
button.addEventListener('click', async () => {
button.disabled = true;
message.textContent = '';
try {
const result = await sdk.subscribe({ segmentId });
if (['subscribed', 'already_subscribed'].includes(result.status)) {
message.textContent = 'You are subscribed!';
} else if (result.status === 'redirected') {
message.textContent = 'Continue subscribing in the app.';
} else if (result.status === 'cancelled') {
message.textContent = 'Subscription cancelled.';
}
} catch (error) {
message.textContent = error.code === 'busy'
? 'Finish the current subscription.' : error.message;
} finally {
button.disabled = false;
}
});
}
Use the block CSS for styling. The SDK displays the standard forms, consents and VK permission request when needed.
Test your button
Save your changes and publish the landing. Open the public link in a browser or the intended platform and click your HTML button. Check channel selection, required consents and how your code displays the result. Subscriptions on the published page are real.
Use the editor preview to inspect HTML and CSS. JavaScript does not run there.
If the landing opens in a regular browser
- The visitor clicks your subscribe button.
- The SDK selects a matching channel. If a choice is needed, the visitor chooses VK, Telegram or MAX from the available project channels.
- The selected platform app opens. Senler verifies the visitor's account there and displays a contact form and required consents when needed. In VK, requesting messaging permission requires a visitor action inside the app: clicking “Subscribe” or confirming the open form.
- Once the flow completes successfully, the SDK reports that the visitor is subscribed. You can now show “You are subscribed” or open the next landing.
On the original page, redirected means “Opened the app”. The subscription is not yet confirmed at this point. Only use subscribed or already_subscribed to show success. If the landing is already open inside the required platform, navigation may not be needed.
See the guide to opening landings for channel and app setup. If VK cannot request messaging permission inside the app, follow the standard subscription dialog instructions.
If the visitor is already subscribed
Calling subscribe again for a segment member returns already_subscribed. You can show “You are already subscribed” or open the same materials as after their first subscription.
Permitted contacts and variables passed with the request are updated on repeat subscriptions too. No new subscription event is created: an automation triggered by a new subscription will not run again. For example, resubmitting a form can update the city in the lead record without restarting the welcome sequence triggered by that event. See the HTML forms guide for field persistence rules.
Show the result after subscribing
When the visitor opens Telegram or VK, JavaScript on the original page may stop running. The landing opened in the app needs to track the continued subscription. The SDK lets you read its current state and receive updates.
Keep the HTML from the button example and replace the contents of the JS tab with the code below. It shows “Thank you” after subscription is confirmed, including when the flow continues inside an app. Replace SEGMENT_ID:
{
const root = document.currentScript.parentElement;
const button = root.querySelector('button');
const message = root.querySelector('[role="status"]');
const sdk = window.SenlerLanding;
const options = { segmentId: 'SEGMENT_ID' };
const nextLandingId = '';
let completedOperationId = null;
const messages = {
idle: 'Click “Subscribe” to continue.',
loading: 'Subscribing…',
awaiting_input: 'Finish your selection or complete the form.',
launching: 'Opening the app…',
redirected: 'Continue subscribing in the app.',
cancelled: 'Subscription cancelled.',
unsubscribed: 'You are unsubscribed.'
};
function showState(operations) {
const operation = operations.find((item) =>
item.segmentId === options.segmentId &&
item.channelId === (options.channelId ?? null)
);
if (!operation) return;
button.disabled = ['loading', 'awaiting_input', 'launching']
.includes(operation.status);
if (['subscribed', 'already_subscribed'].includes(operation.status)) {
message.textContent = 'Thank you! You are subscribed.';
if (nextLandingId && completedOperationId !== operation.operationId) {
completedOperationId = operation.operationId;
sdk.openLanding({ landingId: nextLandingId }).catch((error) => {
message.textContent = 'You are subscribed. Could not open the next page: ' + error.message;
});
}
} else {
message.textContent = messages[operation.status]
?? 'Subscription not completed. See the subscription dialog for details.';
}
}
sdk.onChange(showState);
showState(sdk.getOperations());
button.addEventListener('click', async () => {
if (button.disabled) return;
button.disabled = true;
completedOperationId = null;
message.textContent = 'Subscribing…';
try {
await sdk.subscribe(options);
} catch (error) {
message.textContent = error.message;
} finally {
button.disabled = false;
}
});
}
To open another landing after subscribing, replace the empty nextLandingId string with its internal ID. It must be a different, enabled and published landing in the same project. Navigation preserves platform context. Leave the string empty if you only need the “Thank you” message. Both options also work for visitors who are already subscribed.
In a regular browser, openLanding uses the target landing's custom address when configured. In a MiniApp, navigation stays within the corresponding platform; do not replace landingId with a custom-domain URL.
onChange passes an array of states and runs when they change. It does not call the handler immediately on registration, so the example reads the initial state separately with getOperations(). The code selects the operation for its segment and channel. If you add channelId to options, the same filter will continue to work.
These methods show SDK operations on the current page, including an operation passed when opening the app. They do not return all of the visitor's subscriptions. On an ordinary page visit, the list may be empty until the button is clicked. The listener is removed automatically when the page closes.
SDK methods
| Method | Purpose |
|---|---|
subscribe({ segmentId, channelId?, contactCapture?, contact?, variables? }) | Start subscribing. phone and email in contactCapture are optional booleans |
unsubscribe({ segmentId, channelId? }) | Unsubscribe the current verified visitor; without an authenticated launch, open the page inside the platform first |
openLanding({ landingId }) | Open a published landing in the same project by its internal ID, preserving platform context |
openUrl({ url }) | Open an absolute HTTP(S) URL. Call directly from a click handler |
getOperations() | Read SDK operation states: operationId, segmentId, channelId, status |
onChange(callback) | Receive state updates. Returns an unsubscribe function |
onDispose(callback) | Register cleanup on leaving the page. Returns a function that removes the handler |
Only pass phone and email in contact: non-empty strings up to 120 and 320 characters respectively. Email must be a valid address. Omit an optional empty field; an empty string cannot be used to clear a contact.
The keys in variables are the exact names of permitted lead variables, such as { city: "Moscow", age: 25 }, not lead.city or a definition ID. Values must match the variable types. See HTML form setup for limits and enabling fields. contactCapture opens the standard input fields, while contact passes values already collected; both require those contacts to be permitted in landing settings.
Examples of other actions:
await SenlerLanding.unsubscribe({ segmentId: 'SEGMENT_ID' });
await SenlerLanding.openLanding({ landingId: 'LANDING_ID' });
await SenlerLanding.openUrl({ url: 'https://example.com' });
Results and errors
subscribe and unsubscribe return a Promise with { operationId, status } when the current flow finishes. While the visitor selects a channel, fills out a form or confirms permission, the Promise stays pending. A regular button only needs await and try/catch; global event listeners are not required.
The result status is subscribed, already_subscribed, unsubscribed, cancelled or redirected. Cancellation is a normal outcome, not an error. Technical failures and subscription precondition failures reject the Promise. Opening another app may close the original page: its Promise does not transfer to the new page, and code after await is not guaranteed to execute. Confirmed subscription and handing control to an app are different outcomes.
onChange and getOperations() support advanced intermediate-state displays and resumed launches. The latest operation for each segment and channel is retained on the current page.
loading and launching mean processing; awaiting_input means a form or channel picker. subscribed and already_subscribed confirm subscription, while unsubscribed confirms unsubscription. cancelled means the form or channel picker was closed, or VK permission was declined. redirected only indicates navigation: the original page does not report success without confirmation. After opening the app, the SDK continues with the same segment and options.
unavailable, verification_required, consent_required and error indicate that the operation could not complete. The standard interface shows error details. One subscription flow runs at a time.
Catch errors with try/catch: invalid_argument means invalid parameters, busy means a flow is running, unavailable means no channel is available, not_ready means the page has closed, and timeout means subscription preparation or landing address resolution did not finish within 15 seconds. These requests are cancelled when the page closes; preparation can be retried after a timeout. A subscription that has already been sent is not retried automatically. request_failed means a request or execution failure; consent_required means required consent is missing, and verification_required means the visitor is not verified. An error includes code, message and, if the flow started, operationId. Closing the page rejects an unfinished Promise with not_ready: this does not cancel a subscription request already sent or prove that it failed. Deleting a segment, disabling a landing or replacing its publication can invalidate an existing context.
Subscription statistics belong to the landing and observed content. An arbitrary HTML button does not have a standard button block row in click statistics.
If the button does not work
| What happens | What to check |
|---|---|
| The button does not respond in the editor | Open the published link: the preview does not execute JavaScript |
| An edit has no effect | Wait for saving to finish, publish the changes and reopen the public link. An agent landing is published together with the agent |
The SDK reports invalid parameters (invalid_argument) | Copy the internal segment ID from permission settings. Check that you replaced SEGMENT_ID and did not pass a page address, landingId or a short public ID to subscribe |
| Subscribing or writing fields is forbidden | Allow the selected segment, contacts and variables in HTML form settings, then publish the landing. For an agent segment, check that the agent subscription is published |
No channel is available (unavailable) | Check that the required channel is enabled and configured for landings. If the code specifies channelId, check that particular channel |
| The app opened, but subscription is not confirmed | Complete the steps inside the app: the form, consents and messaging permission. In VK, you may need to click “Subscribe” again inside the app. Navigation itself does not confirm subscription |
The SDK reports an action is already running (busy) | Complete or cancel the open form or channel selection. One subscription flow runs at a time |
| The visitor is already subscribed, but the automation does not run again | A repeat subscription does not create a new event. Check the behavior for segment members |
If the cause remains unclear, save the error message, its code and operationId, if present, together with the public landing link. These details help support find the failed attempt.
Script execution and cleanup
Scripts run on the web, in VK App, Telegram Mini App and MAX Mini App. JS does not execute in the editor preview. Classic <script>, modules and external HTTP(S) files are supported. The JS tab runs after scripts in the same block’s HTML; tags start in block order; loading an external script or module is limited to 15 seconds. State rerenders do not rerun your code. Use addEventListener instead of stripped onclick attributes.
Code can access the public page DOM and data, like Tag Manager. Use trusted scripts. Clean up your timers and global handlers when leaving:
const timer = setInterval(() => {}, 1000);
SenlerLanding.onDispose(() => clearInterval(timer));
onChange listeners are removed automatically. A saved SDK instance from a closed page cannot start operations.