MarzbanSDK
Resources

Migration guide

Step-by-step guides for migrating MarzbanSDK across its breaking releases — v3.0.0's async webhook API, and v4.0.0's WebSocket reconnect lifecycle and destroy() contract.

v3.0.0 → v4.0.0

Shipped in marzban-sdk@4.0.0, bundling three issues that all forced the same major: #84 (a terminal destroy() lifecycle contract), #88 (a WebSocket reconnect state machine replacing sdk.logs's "request that either succeeds or fails" model), and #89 (the resulting public surface — lifecycle callbacks, replay dedup, reconnect options, header auth). See ADR-0015, ADR-0016, and ADR-0017 for the full design rationale.

Breaking changes:

  • A failed first connect*() now rejects instead of resolving. Previously it always resolved with a close handle, even to a socket that never opened.
  • connect*() resolves only once the socket is genuinely open, not merely constructed.
  • onError receives a , never a raw DOM event. err.url is redacted; it never carries the access token.
  • replay: 'dedup' is the new default, suppressing log lines the panel re-delivers after a reconnect. If you relied on seeing every replayed line, opt into replay: 'all'.
  • LogsStreamOptions.maxRetries is gone. WS reconnects use their own internal time budget instead of config.retries; there's no equivalent option to set.
  • Any SDK operation called after destroy() now rejects with instead of silently succeeding or (for WS) silently reconnecting: authorize(), getAuthToken(), logs.connect*(), and webhook.parseWebhook()/handleWebhook()/dispatch(). webhook.on()/once()/off() are unaffected — unsubscribing after shutdown still works.

See the Changelog for full release notes.

1. Handle a rejected first connect

import { isWsError } from 'marzban-sdk'

// v3 — always resolved, even on a failed handshake
const stream = await sdk.logs.connectByCore({ onMessage: handler })

// v4 — a failed first connect now rejects; handle it
try {
  const stream = await sdk.logs.connectByCore({ onMessage: handler })
} catch (err) {
  if (isWsError(err)) console.error('Failed to connect:', err.code, err.message)
}

2. Update onError handlers to expect a WsError

// v3 — a raw DOM event, potentially carrying the token in event.target.url
onError: (event) => console.error(event.message)

// v4 — a typed, redacted error
onError: (error: WsError) => console.error(error.code, error.message)

3. Opt back into every replayed line, if you relied on it

// v4 default is 'dedup' — add this to restore v3's behavior
await sdk.logs.connectByCore({ onMessage: handler, replay: 'all' })

4. Drop maxRetries, use the reconnect option if you need custom timing

// v3
await sdk.logs.connectByCore({ onMessage: handler, maxRetries: 5 })

// v4 — reconnects are budget-based, not attempt-based; shouldReconnect gives full control
await sdk.logs.connectByCore({
  onMessage: handler,
  reconnect: { shouldReconnect: ({ attempt }) => attempt <= 5 },
})

5. Catch SdkDestroyedError for calls that might race a shutdown

import { isSdkDestroyedError } from 'marzban-sdk'

await sdk.destroy()

// v3 — this would silently succeed or hang
// v4 — rejects with SdkDestroyedError
try {
  await sdk.authorize()
} catch (err) {
  if (isSdkDestroyedError(err)) {
    /* expected post-shutdown */
  }
}

v2 → v3.0.0

Shipped in marzban-sdk@3.0.0 by a single commit (fix!: make webhook crypto cross-runtime and keep node:crypto out of browser bundles) — the only breaking-change commit in the 3.x line. Every release from v3.0.1 through v3.3.0 is additive. v4.0.0, however, is another breaking release — see the guide above if you're upgrading past it.

Breaking changes:

  • Webhook verification is now async. parseWebhook and verifyWebhookSignature return Promise instead of being synchronous. Update all call sites to await.
  • Webhook verification is server-side only. Calling handleWebhook or parseWebhook with a secret configured in a browser context now throws (code: WEBHOOK_ENVIRONMENT_ERROR).

See the Changelog for full release notes across every version.

1. Await parseWebhook

// v2
const events = sdk.webhook.parseWebhook(rawBody, sig)

// v3
const events = await sdk.webhook.parseWebhook(rawBody, sig)

2. Await handleWebhook

// v2
sdk.webhook.handleWebhook(rawBody, sig)

// v3
await sdk.webhook.handleWebhook(rawBody, sig)

3. Await verifyWebhookSignature (if called directly)

import { verifyWebhookSignature } from 'marzban-sdk'

// v2
const ok = verifyWebhookSignature(sig, secret, bytes)

// v3
const ok = await verifyWebhookSignature(sig, secret, bytes)

4. Catch WebhookEnvironmentError in browser code

If you were calling signature verification from the browser (not recommended), you'll now receive . Move webhook handling to a server route.

On this page