> ## Documentation Index
> Fetch the complete documentation index at: https://docs.whitebit.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Order Book Depth

> Subscribe to real-time order book depth updates for a trading pair via WhiteBIT WebSocket.

export const RelatedResources = ({children}) => {
  const ref = useRef(null);
  const [visible, setVisible] = useState(false);
  useEffect(() => {
    if (!ref.current) return;
    const el = ref.current;
    if (el.parentElement) {
      el.parentElement.appendChild(el);
    }
    setVisible(true);
  }, []);
  return <div ref={ref} className="related-resources" style={{
    marginTop: "2.5rem",
    paddingTop: "1.5rem",
    borderTop: "1px solid var(--border-color, #e5e7eb)",
    opacity: visible ? 1 : 0,
    transition: "opacity 0.15s ease-in"
  }}>
      <h2 style={{
    marginTop: 0
  }}>Related resources</h2>
      {children}
    </div>;
};

export const exDepthPartialUpdate = {
  "id": null,
  "method": "depth_update",
  "params": [false, {
    "timestamp": 1689600180.516447,
    "update_id": 214403,
    "past_update_id": 214399,
    "asks": [["0.020861", "0"], ["0.020900", "2.5"]],
    "bids": [["0.020844", "5.949"], ["0.020800", "0"]],
    "event_time": 1749026542.817343
  }, "ETH_BTC"]
};

export const exDepthFullReload = {
  "id": null,
  "method": "depth_update",
  "params": [true, {
    "timestamp": 1689600180.516447,
    "asks": [["0.020846", "29.369"], ["0.020850", "15.123"], ["0.020855", "8.456"]],
    "bids": [["0.02083", "9.598"], ["0.020825", "12.345"], ["0.020820", "20.678"]],
    "update_id": 214403,
    "event_time": 1749026542.817343
  }, "ETH_BTC"]
};

export const exDepthSubscribe = {
  "id": 12,
  "method": "depth_subscribe",
  "params": ["ETH_BTC", 100, "0", true]
};

export const exDepthResponse = {
  "id": 11,
  "result": {
    "timestamp": 1689600180.516447,
    "asks": [["0.020846", "29.369"], ["0.020850", "15.123"], ["0.020855", "8.456"]],
    "bids": [["0.02083", "9.598"], ["0.020825", "12.345"], ["0.020820", "20.678"]]
  },
  "error": null
};

export const exDepthRequest = {
  "id": 11,
  "method": "depth_request",
  "params": ["ETH_BTC", 100, "0"]
};

export const depthSubscribeParamsTupleFields = [{
  index: 0,
  field: "market",
  type: "string",
  description: "Market name",
  required: true,
  example: "ETH_BTC"
}, {
  index: 1,
  field: "limit",
  type: "integer",
  description: "Limit",
  required: true,
  example: "100",
  enum: [1, 5, 10, 20, 30, 50, 100]
}, {
  index: 2,
  field: "price_interval",
  type: "string",
  description: "Price interval units. Available values: \"0\" (no interval), \"0.00000001\", \"0.0000001\", \"0.000001\", \"0.00001\", \"0.0001\", \"0.001\", \"0.01\", \"0.1\"",
  required: true,
  example: "0"
}, {
  index: 3,
  field: "multi_depth",
  type: "boolean",
  description: "Multiple subscription flag: true = add subscription, false = unsubscribe from all",
  required: true,
  example: "true"
}];

export const depthRequestParamsTupleFields = [{
  index: 0,
  field: "market",
  type: "string",
  description: "Market name",
  required: true,
  example: "ETH_BTC"
}, {
  index: 1,
  field: "limit",
  type: "integer",
  description: "Limit (max 100)",
  required: true,
  example: "100"
}, {
  index: 2,
  field: "price_interval",
  type: "string",
  description: "Price interval units. Available values: \"0\" (no interval), \"0.00000001\", \"0.0000001\", \"0.000001\", \"0.00001\", \"0.0001\", \"0.001\", \"0.01\", \"0.1\"",
  required: true,
  example: "0"
}];

export const depthUpdateData = [{
  name: "timestamp",
  type: "number",
  required: true,
  description: "Timestamp from matchengine"
}, {
  name: "update_id",
  type: "integer",
  description: "Per-market sequence counter. Increments on any change to the full order book for this market, including changes that do not produce a `depth_update` on this subscription. On a keepalive snapshot the value may exceed the last incremental delta's `update_id`."
}, {
  name: "past_update_id",
  type: "integer",
  description: "Previous update ID (present in incremental updates only, not in first snapshot)"
}, {
  name: "asks",
  type: "array",
  required: true,
  description: ""
}, {
  name: "bids",
  type: "array",
  required: true,
  description: ""
}, {
  name: "event_time",
  type: "number",
  required: true,
  description: "Event time"
}];

export const channelMeta = {
  "authRequired": false,
  "rateLimits": {
    "connectionsPerMinute": 1000,
    "requestsPerMinute": 200
  },
  "errorCodes": "standard"
};

export const channelOperations = [{
  name: "Query",
  send: "depth_request",
  receive: "Full order book snapshot",
  push: null
}, {
  name: "Subscribe",
  send: "depth_subscribe",
  receive: "Confirmation (status: success)",
  push: "depth_update — full snapshot (first), then incremental updates"
}, {
  name: "Unsubscribe",
  send: "depth_unsubscribe",
  receive: "Confirmation (status: success)",
  push: null
}];

export const WsErrorCodes = ({errorCodes}) => {
  if (Array.isArray(errorCodes)) {
    return <div>
        <table>
          <thead>
            <tr>
              <th>Code</th>
              <th>Message</th>
              <th>Description</th>
            </tr>
          </thead>
          <tbody>
            {errorCodes.map((err, i) => <tr key={i}>
                <td><code>{err.code}</code></td>
                <td>{err.message}</td>
                <td>{err.description}</td>
              </tr>)}
          </tbody>
        </table>
        <p>
          Standard WebSocket error codes apply. See <a href="/websocket/overview">WebSocket overview</a> for error code reference.
        </p>
      </div>;
  }
  return <p>
      Standard WebSocket error codes apply. See <a href="/websocket/overview">WebSocket overview</a> for error code reference.
    </p>;
};

export const WsRateLimits = ({connectionsPerMinute, requestsPerMinute}) => {
  return <p>
      Standard connection-level rate limits apply. See{' '}
      <a href="/websocket/rate-limits">WebSocket Rate Limits</a> for details.
    </p>;
};

export const WsAuthBadge = ({required}) => {
  if (!required) {
    return <p><strong>Authentication:</strong> None. No authentication required.</p>;
  }
  return <div>
      <Warning>
        This is a private channel.{' '}
        <a href="/websocket/authentication">Authorize</a> the WebSocket
        connection before subscribing.
      </Warning>
      <p>
        <strong>Authentication:</strong> Required.{' '}
        <a href="/websocket/authentication">Authorize</a> the WebSocket
        connection before subscribing.
      </p>
    </div>;
};

export const WsTupleTable = ({title, fields}) => {
  const [isDark, setIsDark] = useState(typeof document !== 'undefined' ? document.documentElement.classList.contains('dark') : true);
  useEffect(() => {
    const check = () => setIsDark(document.documentElement.classList.contains('dark'));
    check();
    const observer = new MutationObserver(check);
    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ['class']
    });
    return () => observer.disconnect();
  }, []);
  const T = isDark ? {
    border: '#374151',
    borderSubtle: '#1f2937',
    headerBg: '#1f2937',
    headerText: '#9ca3af',
    titleBg: '#1f2937',
    titleText: '#d1d5db',
    indexText: '#60a5fa',
    fieldText: '#e5e7eb',
    fieldBg: '#374151',
    descText: '#9ca3af',
    exampleText: '#d1d5db',
    exampleBg: 'rgb(55 65 81 / 0.4)',
    enumText: '#fbbf24',
    reqYes: '#f3f4f6',
    reqNo: '#4b5563'
  } : {
    border: '#e5e7eb',
    borderSubtle: '#f3f4f6',
    headerBg: '#f9fafb',
    headerText: '#6b7280',
    titleBg: '#f9fafb',
    titleText: '#374151',
    indexText: '#2563eb',
    fieldText: '#1f2937',
    fieldBg: '#f3f4f6',
    descText: '#6b7280',
    exampleText: '#374151',
    exampleBg: '#f3f4f6',
    enumText: '#92400e',
    reqYes: '#111827',
    reqNo: '#d1d5db'
  };
  const MONO = 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace';
  const HEADER = {
    padding: '0.5rem 0.75rem',
    fontSize: '0.6875rem',
    fontWeight: 600,
    textTransform: 'uppercase',
    letterSpacing: '0.05em',
    whiteSpace: 'nowrap',
    color: T.headerText,
    backgroundColor: T.headerBg
  };
  const CELL = {
    padding: '0.5rem 0.75rem',
    fontSize: '0.8125rem',
    display: 'flex',
    alignItems: 'center',
    minWidth: 0
  };
  const hasExample = fields.some(f => f.example !== undefined);
  const hasRequired = fields.some(f => f.required === true);
  const gridTemplateColumns = ['minmax(60px, auto)', 'minmax(100px, auto)', ...hasRequired ? ['minmax(70px, auto)'] : [], ...hasExample ? ['minmax(140px, auto)'] : [], '1fr'].join(' ');
  function formatDesc(f) {
    if (f.enumLabels && typeof f.enumLabels === 'object') {
      const mapping = Object.entries(f.enumLabels).map(([v, label]) => `${v} = ${label}`).join(', ');
      return f.description ? `${f.description}. ${mapping}` : mapping;
    }
    if (f.enum && f.enum.length > 0 && f.description) {
      const hasMapping = f.enum.some(v => f.description.includes(`${v}=`) || f.description.includes(`${v} =`));
      if (hasMapping) return f.description;
    }
    return f.description || '';
  }
  return <div style={{
    width: '100%',
    margin: '0.75rem 0',
    borderRadius: '0.5rem',
    border: `1px solid ${T.border}`,
    overflow: 'hidden',
    fontSize: '0.8125rem'
  }}>
      {title && <div style={{
    padding: '0.5rem 0.75rem',
    fontSize: '0.75rem',
    fontWeight: 600,
    letterSpacing: '0.02em',
    backgroundColor: T.titleBg,
    borderBottom: `1px solid ${T.border}`,
    color: T.titleText
  }}>
          {title}
        </div>}
      <div style={{
    display: 'grid',
    gridTemplateColumns,
    width: '100%',
    overflowX: 'auto'
  }}>

        <div style={{
    ...HEADER,
    borderBottom: `1px solid ${T.border}`
  }}>Index</div>
        <div style={{
    ...HEADER,
    borderBottom: `1px solid ${T.border}`
  }}>Field</div>
        {hasRequired && <div style={{
    ...HEADER,
    borderBottom: `1px solid ${T.border}`
  }}>Required</div>}
        {hasExample && <div style={{
    ...HEADER,
    borderBottom: `1px solid ${T.border}`
  }}>Example</div>}
        <div style={{
    ...HEADER,
    borderBottom: `1px solid ${T.border}`
  }}>Description</div>

        {fields.map((f, i) => {
    const borderTop = i > 0 ? `1px solid ${T.borderSubtle}` : undefined;
    const desc = formatDesc(f);
    return [<div key={`${i}-i`} style={{
      ...CELL,
      whiteSpace: 'nowrap',
      borderTop
    }}>
              <code style={{
      fontFamily: MONO,
      fontSize: '0.75rem',
      color: T.indexText
    }}>[{f.index}]</code>
            </div>, <div key={`${i}-f`} style={{
      ...CELL,
      whiteSpace: 'nowrap',
      borderTop
    }}>
              <span style={{
      padding: '0.125rem 0.375rem',
      borderRadius: '0.25rem',
      fontSize: '0.75rem',
      fontFamily: MONO,
      backgroundColor: T.fieldBg,
      color: T.fieldText
    }}>
                {f.field}
              </span>
            </div>, ...hasRequired ? [<div key={`${i}-r`} style={{
      ...CELL,
      borderTop,
      fontWeight: f.required ? 500 : 400,
      color: f.required ? T.reqYes : T.reqNo
    }}>
                {f.required ? 'Yes' : '—'}
              </div>] : [], ...hasExample ? [<div key={`${i}-e`} style={{
      ...CELL,
      whiteSpace: 'nowrap',
      borderTop
    }}>
                {f.example !== undefined ? <code style={{
      fontFamily: MONO,
      fontSize: '0.75rem',
      backgroundColor: T.exampleBg,
      color: T.exampleText,
      padding: '0.125rem 0.375rem',
      borderRadius: '0.25rem'
    }}>
                    {f.example}
                  </code> : null}
              </div>] : [], <div key={`${i}-d`} style={{
      ...CELL,
      borderTop,
      color: T.descText
    }}>
              {desc}
            </div>];
  })}

      </div>
    </div>;
};

export const WsSubscribeSteps = ({subscribe, confirmation, update, unsubscribe, subscribeNote, updateNote, updateExtra, unsubscribeNote}) => {
  const [isDark, setIsDark] = useState(typeof document !== 'undefined' ? document.documentElement.classList.contains('dark') : true);
  useEffect(() => {
    const check = () => setIsDark(document.documentElement.classList.contains('dark'));
    check();
    const observer = new MutationObserver(check);
    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ['class']
    });
    return () => observer.disconnect();
  }, []);
  const TOKEN_COLORS = isDark ? {
    key: '#79c0ff',
    string: '#a5d6ff',
    number: '#e3b341',
    boolean: '#ff7b72',
    null: '#8b949e',
    punctuation: '#c9d1d9',
    ws: '#c9d1d9'
  } : {
    key: '#0550ae',
    string: '#0a3069',
    number: '#953800',
    boolean: '#cf222e',
    null: '#6e7781',
    punctuation: '#24292f',
    ws: '#24292f'
  };
  const tokenize = json => {
    const tokens = [];
    let i = 0;
    while (i < json.length) {
      if ((/\s/).test(json[i])) {
        let ws = '';
        while (i < json.length && (/\s/).test(json[i])) ws += json[i++];
        tokens.push({
          type: 'ws',
          value: ws
        });
        continue;
      }
      if (json[i] === '"') {
        let str = '"';
        i++;
        while (i < json.length) {
          if (json[i] === '\\') {
            str += json[i] + json[i + 1];
            i += 2;
          } else if (json[i] === '"') {
            str += '"';
            i++;
            break;
          } else str += json[i++];
        }
        let j = i;
        while (j < json.length && (/[ \t]/).test(json[j])) j++;
        tokens.push({
          type: json[j] === ':' ? 'key' : 'string',
          value: str
        });
        continue;
      }
      if (json[i] === '-' || (/\d/).test(json[i])) {
        let num = '';
        if (json[i] === '-') num += json[i++];
        while (i < json.length && (/[\d.eE+\-]/).test(json[i])) num += json[i++];
        tokens.push({
          type: 'number',
          value: num
        });
        continue;
      }
      if (json.slice(i, i + 4) === 'true') {
        tokens.push({
          type: 'boolean',
          value: 'true'
        });
        i += 4;
        continue;
      }
      if (json.slice(i, i + 5) === 'false') {
        tokens.push({
          type: 'boolean',
          value: 'false'
        });
        i += 5;
        continue;
      }
      if (json.slice(i, i + 4) === 'null') {
        tokens.push({
          type: 'null',
          value: 'null'
        });
        i += 4;
        continue;
      }
      tokens.push({
        type: 'punctuation',
        value: json[i++]
      });
    }
    return tokens;
  };
  const JsonBlock = ({data}) => {
    const json = JSON.stringify(data, null, 2);
    const tokens = tokenize(json);
    return <div className="border border-gray-200 dark:border-[#30363d]" style={{
      borderRadius: '0.5rem',
      overflow: 'hidden',
      marginBottom: '0.75rem'
    }}>
        <div className="bg-gray-50 dark:bg-[#161b22] border-b border-gray-200 dark:border-[#30363d] text-gray-500 dark:text-[#8b949e]" style={{
      padding: '0.25rem 1rem',
      fontSize: '0.7rem',
      fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace'
    }}>
          json
        </div>
        <div className="bg-white dark:bg-[#0d1117]" style={{
      margin: 0,
      padding: '0.875rem 1.125rem',
      overflowX: 'auto',
      fontSize: '0.8125rem',
      lineHeight: '1.65',
      fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace',
      whiteSpace: 'pre'
    }}>
          {tokens.map((t, idx) => <span key={idx} style={{
      color: TOKEN_COLORS[t.type]
    }}>{t.value}</span>)}
        </div>
      </div>;
  };
  const CIRCLE = 32;
  const WsStep = ({number, title, isLast, children}) => <div style={{
    display: 'flex',
    gap: '1rem'
  }}>
      <div style={{
    display: 'flex',
    flexDirection: 'column',
    alignItems: 'center',
    flexShrink: 0,
    width: CIRCLE
  }}>
        <div className="bg-gray-100 dark:bg-gray-700 border-gray-300 dark:border-gray-600 text-gray-900 dark:text-gray-100" style={{
    width: CIRCLE,
    height: CIRCLE,
    borderRadius: '50%',
    border: '1.5px solid',
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'center',
    fontSize: '0.8125rem',
    fontWeight: 600,
    flexShrink: 0
  }}>
          {number}
        </div>
        {!isLast && <div className="bg-gray-200 dark:bg-gray-700" style={{
    width: 2,
    flex: 1,
    minHeight: '1.5rem'
  }} />}
      </div>
      <div style={{
    flex: 1,
    paddingBottom: isLast ? '0.5rem' : '1.75rem',
    paddingTop: '4px',
    minWidth: 0
  }}>
        <p style={{
    fontWeight: 600,
    fontSize: '1rem',
    margin: '0 0 0.75rem 0',
    lineHeight: `${CIRCLE}px`
  }}>
          {title}
        </p>
        {children}
      </div>
    </div>;
  return <div style={{
    margin: '1.25rem 0'
  }}>
      <WsStep number={1} title="Send subscription request">
        {subscribeNote && <p style={{
    margin: '0 0 0.75rem 0'
  }}>{subscribeNote}</p>}
        <JsonBlock data={subscribe} />
      </WsStep>

      <WsStep number={2} title="Receive confirmation">
        <JsonBlock data={confirmation} />
      </WsStep>

      <WsStep number={3} title="Receive real-time updates">
        {updateNote && <p style={{
    margin: '0 0 0.75rem 0'
  }}>{updateNote}</p>}
        <JsonBlock data={update} />
        {updateExtra}
      </WsStep>

      <WsStep number={4} title="Unsubscribe" isLast>
        {unsubscribeNote && <p style={{
    margin: '0 0 0.75rem 0'
  }}>{unsubscribeNote}</p>}
        <JsonBlock data={unsubscribe} />
      </WsStep>
    </div>;
};

export const WsMessageExample = ({data, title}) => {
  const [isDark, setIsDark] = useState(typeof document !== 'undefined' ? document.documentElement.classList.contains('dark') : true);
  useEffect(() => {
    const check = () => setIsDark(document.documentElement.classList.contains('dark'));
    check();
    const observer = new MutationObserver(check);
    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ['class']
    });
    return () => observer.disconnect();
  }, []);
  const COLORS = isDark ? {
    key: '#79c0ff',
    string: '#a5d6ff',
    number: '#e3b341',
    boolean: '#ff7b72',
    null: '#8b949e',
    punctuation: '#c9d1d9',
    ws: '#c9d1d9'
  } : {
    key: '#0550ae',
    string: '#0a3069',
    number: '#953800',
    boolean: '#cf222e',
    null: '#6e7781',
    punctuation: '#24292f',
    ws: '#24292f'
  };
  const tokenize = json => {
    const tokens = [];
    let i = 0;
    while (i < json.length) {
      if ((/\s/).test(json[i])) {
        let ws = '';
        while (i < json.length && (/\s/).test(json[i])) ws += json[i++];
        tokens.push({
          type: 'ws',
          value: ws
        });
        continue;
      }
      if (json[i] === '"') {
        let str = '"';
        i++;
        while (i < json.length) {
          if (json[i] === '\\') {
            str += json[i] + json[i + 1];
            i += 2;
          } else if (json[i] === '"') {
            str += '"';
            i++;
            break;
          } else {
            str += json[i++];
          }
        }
        let j = i;
        while (j < json.length && (/[ \t]/).test(json[j])) j++;
        const isKey = json[j] === ':';
        tokens.push({
          type: isKey ? 'key' : 'string',
          value: str
        });
        continue;
      }
      if (json[i] === '-' || (/\d/).test(json[i])) {
        let num = '';
        if (json[i] === '-') num += json[i++];
        while (i < json.length && (/[\d.eE+\-]/).test(json[i])) num += json[i++];
        tokens.push({
          type: 'number',
          value: num
        });
        continue;
      }
      if (json.slice(i, i + 4) === 'true') {
        tokens.push({
          type: 'boolean',
          value: 'true'
        });
        i += 4;
        continue;
      }
      if (json.slice(i, i + 5) === 'false') {
        tokens.push({
          type: 'boolean',
          value: 'false'
        });
        i += 5;
        continue;
      }
      if (json.slice(i, i + 4) === 'null') {
        tokens.push({
          type: 'null',
          value: 'null'
        });
        i += 4;
        continue;
      }
      tokens.push({
        type: 'punctuation',
        value: json[i++]
      });
    }
    return tokens;
  };
  const json = JSON.stringify(data, null, 2);
  const tokens = tokenize(json);
  return <div style={{
    marginBottom: '1rem'
  }}>
      {title && <p style={{
    fontWeight: 600,
    fontSize: '0.875rem',
    marginTop: '1rem',
    marginBottom: '0.5rem'
  }}>
          {title}
        </p>}
      <div className="border border-gray-200 dark:border-[#30363d]" style={{
    position: 'relative',
    borderRadius: '0.5rem',
    overflow: 'hidden'
  }}>
        <div className="bg-gray-50 dark:bg-[#161b22] border-b border-gray-200 dark:border-[#30363d]" style={{
    display: 'flex',
    alignItems: 'center',
    justifyContent: 'space-between',
    padding: '0.375rem 1rem'
  }}>
          <span className="text-gray-500 dark:text-[#8b949e]" style={{
    fontSize: '0.75rem',
    fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace'
  }}>
            json
          </span>
        </div>
        <div className="bg-white dark:bg-[#0d1117]" style={{
    margin: 0,
    padding: '1rem 1.25rem',
    overflowX: 'auto',
    fontSize: '0.8125rem',
    lineHeight: '1.7',
    fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace',
    whiteSpace: 'pre'
  }}>
          {tokens.map((t, idx) => <span key={idx} style={{
    color: COLORS[t.type]
  }}>{t.value}</span>)}
        </div>
      </div>
    </div>;
};

export const WsSchemaTable = ({title, fields}) => {
  const [isDark, setIsDark] = useState(typeof document !== 'undefined' ? document.documentElement.classList.contains('dark') : true);
  useEffect(() => {
    const check = () => setIsDark(document.documentElement.classList.contains('dark'));
    check();
    const observer = new MutationObserver(check);
    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ['class']
    });
    return () => observer.disconnect();
  }, []);
  const T = isDark ? {
    border: '#374151',
    borderSubtle: '#1f2937',
    headerBg: '#1f2937',
    headerText: '#9ca3af',
    titleBg: '#1f2937',
    titleText: '#d1d5db',
    fieldBg: '#374151',
    fieldText: '#e5e7eb',
    reqYes: '#f3f4f6',
    reqNo: '#4b5563',
    descText: '#9ca3af',
    emptyText: '#4b5563',
    enumBg: 'rgb(120 53 15 / 0.3)',
    enumText: '#fbbf24'
  } : {
    border: '#e5e7eb',
    borderSubtle: '#f3f4f6',
    headerBg: '#f9fafb',
    headerText: '#6b7280',
    titleBg: '#f9fafb',
    titleText: '#374151',
    fieldBg: '#f3f4f6',
    fieldText: '#1f2937',
    reqYes: '#111827',
    reqNo: '#d1d5db',
    descText: '#6b7280',
    emptyText: '#d1d5db',
    enumBg: '#fffbeb',
    enumText: '#92400e'
  };
  const TYPE_STYLE = isDark ? {
    string: {
      backgroundColor: 'rgb(88 28 135 / 0.25)',
      color: '#c084fc'
    },
    integer: {
      backgroundColor: 'rgb(154 52 18 / 0.25)',
      color: '#fb923c'
    },
    number: {
      backgroundColor: 'rgb(154 52 18 / 0.25)',
      color: '#fb923c'
    },
    boolean: {
      backgroundColor: 'rgb(30 64 175 / 0.25)',
      color: '#60a5fa'
    },
    array: {
      backgroundColor: 'rgb(17 94 89 / 0.25)',
      color: '#2dd4bf'
    },
    object: {
      backgroundColor: 'rgb(55 65 81 / 0.4)',
      color: '#9ca3af'
    },
    null: {
      backgroundColor: 'rgb(55 65 81 / 0.4)',
      color: '#9ca3af'
    }
  } : {
    string: {
      backgroundColor: '#faf5ff',
      color: '#6b21a8'
    },
    integer: {
      backgroundColor: '#fff7ed',
      color: '#9a3412'
    },
    number: {
      backgroundColor: '#fff7ed',
      color: '#9a3412'
    },
    boolean: {
      backgroundColor: '#eff6ff',
      color: '#1e40af'
    },
    array: {
      backgroundColor: '#f0fdfa',
      color: '#115e59'
    },
    object: {
      backgroundColor: '#f3f4f6',
      color: '#374151'
    },
    null: {
      backgroundColor: '#f3f4f6',
      color: '#374151'
    }
  };
  const MONO = 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace';
  const HEADER = {
    padding: '0.5rem 1rem',
    fontSize: '0.6875rem',
    fontWeight: 600,
    textTransform: 'uppercase',
    letterSpacing: '0.05em',
    whiteSpace: 'nowrap',
    color: T.headerText,
    backgroundColor: T.headerBg,
    borderBottom: `1px solid ${T.border}`
  };
  const CELL = {
    padding: '0.625rem 1rem',
    fontSize: '0.8125rem',
    display: 'flex',
    alignItems: 'center',
    minWidth: 0
  };
  const hasRequired = fields.some(f => f.required === true);
  const hasEnum = fields.some(f => f.enum && f.enum.length > 0);
  const gridTemplateColumns = ['minmax(120px, auto)', 'minmax(70px, auto)', ...hasRequired ? ['minmax(70px, auto)'] : [], ...hasEnum ? ['minmax(120px, auto)'] : [], '1fr'].join(' ');
  return <div style={{
    margin: '1.25rem 0',
    borderRadius: '0.5rem',
    border: `1px solid ${T.border}`,
    overflow: 'hidden',
    fontSize: '0.8125rem'
  }}>
      {title && <div style={{
    padding: '0.5rem 1rem',
    fontSize: '0.75rem',
    fontWeight: 600,
    letterSpacing: '0.02em',
    backgroundColor: T.titleBg,
    borderBottom: `1px solid ${T.border}`,
    color: T.titleText
  }}>
          {title}
        </div>}
      <div style={{
    display: 'grid',
    gridTemplateColumns,
    width: '100%',
    overflowX: 'auto'
  }}>

        <div style={HEADER}>Field</div>
        <div style={HEADER}>Type</div>
        {hasRequired && <div style={HEADER}>Required</div>}
        {hasEnum && <div style={HEADER}>Values</div>}
        <div style={HEADER}>Description</div>

        {fields.map((f, i) => {
    const borderTop = `1px solid ${i === 0 ? T.border : T.borderSubtle}`;
    const typeStyle = TYPE_STYLE[f.type] || TYPE_STYLE.object;
    return [<div key={`${i}-n`} style={{
      ...CELL,
      whiteSpace: 'nowrap',
      borderTop
    }}>
              <span style={{
      padding: '0.125rem 0.375rem',
      borderRadius: '0.25rem',
      fontSize: '0.75rem',
      fontFamily: MONO,
      backgroundColor: T.fieldBg,
      color: T.fieldText
    }}>
                {f.name}
              </span>
            </div>, <div key={`${i}-t`} style={{
      ...CELL,
      whiteSpace: 'nowrap',
      borderTop
    }}>
              <span style={{
      padding: '0.125rem 0.375rem',
      borderRadius: '0.25rem',
      fontSize: '0.75rem',
      whiteSpace: 'nowrap',
      fontFamily: MONO,
      ...typeStyle
    }}>
                {f.type}
              </span>
            </div>, ...hasRequired ? [<div key={`${i}-r`} style={{
      ...CELL,
      borderTop,
      fontWeight: f.required ? 500 : 400,
      color: f.required ? T.reqYes : T.reqNo
    }}>
                {f.required ? 'Yes' : '—'}
              </div>] : [], ...hasEnum ? [<div key={`${i}-e`} style={{
      ...CELL,
      borderTop,
      flexWrap: 'wrap',
      gap: '0.25rem'
    }}>
                {f.enum && f.enum.length > 0 ? f.enum.map((v, j) => <span key={j} style={{
      padding: '0.125rem 0.375rem',
      borderRadius: '0.25rem',
      fontSize: '0.75rem',
      whiteSpace: 'nowrap',
      fontFamily: MONO,
      backgroundColor: T.enumBg,
      color: T.enumText
    }}>
                        {String(v)}
                      </span>) : <span style={{
      color: T.emptyText
    }}>—</span>}
              </div>] : [], <div key={`${i}-d`} style={{
      ...CELL,
      borderTop,
      color: T.descText
    }}>
              {f.description}
            </div>];
  })}

      </div>
    </div>;
};

export const WsChannelOverview = ({operations}) => {
  const hasPush = operations.some(op => op.push);
  const Th = ({children}) => <th className="text-left text-gray-500 dark:text-gray-400 border-b border-gray-200 dark:border-gray-700" style={{
    padding: "0.5rem 1rem",
    fontSize: "0.6875rem",
    fontWeight: 600,
    textTransform: "uppercase",
    letterSpacing: "0.05em",
    whiteSpace: "nowrap"
  }}>
      {children}
    </th>;
  const PushBadge = ({value}) => {
    const dashIdx = value.indexOf(" — ");
    const method = dashIdx !== -1 ? value.slice(0, dashIdx) : value;
    const desc = dashIdx !== -1 ? value.slice(dashIdx + 3) : null;
    return <span style={{
      display: "inline-flex",
      alignItems: "center",
      gap: "0.375rem",
      flexWrap: "wrap"
    }}>
        <code className="bg-blue-50 dark:bg-blue-900/20 text-blue-800 dark:text-blue-400" style={{
      padding: "0.125rem 0.375rem",
      borderRadius: "0.25rem",
      fontSize: "0.75rem",
      whiteSpace: "nowrap"
    }}>
          {method}
        </code>
        {desc && <span className="text-gray-500 dark:text-gray-400" style={{
      fontSize: "0.75rem"
    }}>
            — {desc}
          </span>}
      </span>;
  };
  return <div style={{
    margin: "1.25rem 0",
    borderRadius: "0.5rem",
    border: "1px solid",
    overflow: "hidden",
    fontSize: "0.8125rem"
  }} className="border-gray-200 dark:border-gray-700">
      <table className="w-full" style={{
    margin: 0,
    borderCollapse: "collapse",
    tableLayout: "auto"
  }}>
        <thead>
          <tr className="bg-gray-50 dark:bg-gray-800/60 border-b border-gray-200 dark:border-gray-700">
            {Th({
    children: "Operation"
  })}
            {Th({
    children: <span><span style={{
      color: "#16a34a"
    }}>→</span> You send</span>
  })}
            {Th({
    children: "← Server responds"
  })}
            {hasPush && Th({
    children: <span><span style={{
      color: "#2563eb"
    }}>⟵</span> Server pushes</span>
  })}
          </tr>
        </thead>
        <tbody>
          {operations.map((op, i) => <tr key={i} className="border-b border-gray-100 dark:border-gray-800" style={{
    borderBottom: i === operations.length - 1 ? "none" : undefined
  }}>
              {}
              <td className="text-gray-900 dark:text-gray-100" style={{
    padding: "0.625rem 1rem",
    fontWeight: 500,
    whiteSpace: "nowrap"
  }}>
                {op.name}
              </td>

              {}
              <td style={{
    padding: "0.625rem 1rem",
    whiteSpace: "nowrap"
  }}>
                <code className="bg-green-50 dark:bg-green-900/20 text-green-800 dark:text-green-400" style={{
    padding: "0.125rem 0.375rem",
    borderRadius: "0.25rem",
    fontSize: "0.75rem"
  }}>
                  {op.send}
                </code>
              </td>

              {}
              <td className="text-gray-500 dark:text-gray-400" style={{
    padding: "0.625rem 1rem"
  }}>
                {op.receive}
              </td>

              {}
              {hasPush && <td style={{
    padding: "0.625rem 1rem"
  }}>
                  {op.push ? PushBadge({
    value: op.push
  }) : <span className="text-gray-300 dark:text-gray-600">—</span>}
                </td>}
            </tr>)}
        </tbody>
      </table>
    </div>;
};

export const RegionBaseUrl = ({className = "", showBaseUrl = true}) => {
  const [region, setRegionState] = useState(() => {
    if (typeof window !== 'undefined') {
      return localStorage.getItem("api-region-preference") || "com";
    }
    return "com";
  });
  const [mounted, setMounted] = useState(false);
  const observerRef = useRef(null);
  const isSyncingRef = useRef(false);
  const updateAllContentOnPage = targetRegion => {
    try {
      const domainFrom = targetRegion === "eu" ? "whitebit.com" : "whitebit.eu";
      const domainTo = targetRegion === "eu" ? "whitebit.eu" : "whitebit.com";
      const links = document.querySelectorAll('a');
      links.forEach(link => {
        let href = link.getAttribute('href');
        if (href && href.includes(domainFrom)) {
          link.setAttribute('href', href.replace(domainFrom, domainTo));
        }
      });
      const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, {
        acceptNode: node => {
          if (node.parentElement?.closest('.region-toggle-component')) {
            return NodeFilter.FILTER_REJECT;
          }
          return node.textContent.includes(domainFrom) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
        }
      });
      let currentNode;
      while (currentNode = walker.nextNode()) {
        currentNode.textContent = currentNode.textContent.replace(new RegExp(domainFrom, 'g'), domainTo);
      }
      console.log(`[RegionSync] Global content updated to ${domainTo}`);
    } catch (e) {
      console.error("[RegionSync] Error updating content:", e);
    }
  };
  const updateRegion = (newRegion, source) => {
    if (region === newRegion) return;
    console.log(`[RegionBaseUrl] Updating to "${newRegion}" (Source: ${source})`);
    if (source === 'observer') {
      isSyncingRef.current = true;
      setTimeout(() => isSyncingRef.current = false, 1000);
    }
    setRegionState(newRegion);
    localStorage.setItem("api-region-preference", newRegion);
    updateAllContentOnPage(newRegion);
    if (source === 'user-click') {
      window.dispatchEvent(new CustomEvent("regionChange", {
        detail: newRegion
      }));
      attemptToUpdateNativeDropdown(newRegion, 0);
      setTimeout(() => attemptToUpdateNativeDropdown(newRegion, 1), 500);
      setTimeout(() => attemptToUpdateNativeDropdown(newRegion, 2), 1500);
    }
  };
  const attemptToUpdateNativeDropdown = (targetRegion, attempt) => {
    if (isSyncingRef.current) return;
    try {
      const targetUrl = targetRegion === "eu" ? "https://whitebit.eu" : "https://whitebit.com";
      const targetDesc = targetRegion === "eu" ? "EU Server" : "Production Server";
      const selects = document.querySelectorAll('select');
      for (const select of selects) {
        if (select.innerHTML.includes('whitebit.com') || select.innerHTML.includes('whitebit.eu')) {
          select.value = targetUrl;
          select.dispatchEvent(new Event('change', {
            bubbles: true
          }));
          return;
        }
      }
      const buttons = Array.from(document.querySelectorAll('button, [role="combobox"]'));
      const serverSelector = buttons.find(btn => {
        if (btn.closest('a') || btn.closest('[class*="card"]') || btn.closest('nav')) {
          return false;
        }
        const txt = btn.textContent || "";
        const isServerDropdown = (txt.includes('Production Server') || txt.includes('EU Server') || txt.includes('WhiteBIT Global Server') || txt.includes('WhiteBIT EU Server')) && !txt.includes('Run') && !txt.includes('Send') || btn.getAttribute('role') === 'combobox';
        return isServerDropdown;
      });
      if (serverSelector) {
        const currentText = serverSelector.textContent || "";
        if (currentText.includes(targetDesc)) return;
        serverSelector.click();
        setTimeout(() => {
          const options = document.querySelectorAll('[role="option"], li, button');
          for (const opt of options) {
            const optText = opt.textContent || "";
            if (optText.includes(targetDesc) || optText.includes(targetUrl)) {
              opt.click();
              return;
            }
          }
        }, 100);
      }
    } catch (e) {
      console.error("[Sync] Error:", e);
    }
  };
  useEffect(() => {
    setMounted(true);
    updateAllContentOnPage(region);
    const handleStorageChange = e => {
      if (e.key === "api-region-preference" && e.newValue) {
        updateRegion(e.newValue, 'storage');
      }
    };
    const handleRegionChange = e => {
      if (e.detail !== region) {
        updateRegion(e.detail, 'event');
      }
    };
    window.addEventListener("storage", handleStorageChange);
    window.addEventListener("regionChange", handleRegionChange);
    observerRef.current = new MutationObserver(mutations => {
      if (isSyncingRef.current) return;
      updateAllContentOnPage(region);
      for (const mutation of mutations) {
        if (mutation.type !== 'childList' && mutation.type !== 'characterData') continue;
        const target = mutation.target;
        const el = target.nodeType === Node.TEXT_NODE ? target.parentElement : target;
        if (el && (el.getAttribute('role') === 'option' || el.closest('[role="listbox"]'))) continue;
        const text = target.textContent || "";
        if (text.includes('WhiteBIT EU Server') || text.includes('https://whitebit.eu') && text.includes('Server')) {
          if (el && el.tagName !== 'A' && !el.closest('.region-toggle-component')) {
            if (region !== 'eu') updateRegion('eu', 'observer');
          }
        } else if (text.includes('WhiteBIT Global Server') || text.includes('https://whitebit.com') && text.includes('Server')) {
          if (el && el.tagName !== 'A' && !el.closest('.region-toggle-component')) {
            if (region !== 'com') updateRegion('com', 'observer');
          }
        }
      }
    });
    observerRef.current.observe(document.body, {
      childList: true,
      subtree: true,
      characterData: true
    });
    if (typeof window !== 'undefined') {
      const current = localStorage.getItem("api-region-preference");
      if (current) attemptToUpdateNativeDropdown(current, 'init');
    }
    return () => {
      window.removeEventListener("storage", handleStorageChange);
      window.removeEventListener("regionChange", handleRegionChange);
      if (observerRef.current) observerRef.current.disconnect();
    };
  }, [region]);
  const apiBaseUrl = region === "eu" ? "https://whitebit.eu" : "https://whitebit.com";
  if (!mounted) return null;
  return <div className={`flex items-center gap-2 flex-wrap my-4 region-toggle-component ${className}`}>
            <span className="text-sm text-gray-500 dark:text-gray-400 font-mono">
                Base URL
            </span>
            <span className="text-sm text-gray-400">(</span>
            <div className="inline-flex bg-gray-100 dark:bg-gray-800 rounded-lg p-0.5 border border-gray-200 dark:border-gray-700">
                <button onClick={() => updateRegion("com", "user-click")} className={`px-2 py-0.5 text-xs font-medium rounded-md transition-all ${region === "com" ? "bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 shadow-sm" : "text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200"}`}>
                    .com
                </button>
                <button onClick={() => updateRegion("eu", "user-click")} className={`px-2 py-0.5 text-xs font-medium rounded-md transition-all ${region === "eu" ? "bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 shadow-sm" : "text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200"}`}>
                    .eu
                </button>
            </div>
            <span className="text-sm text-gray-400">)</span>
            {showBaseUrl && <>
                    <span className="text-sm text-gray-400">:</span>
                    <a href={apiBaseUrl} target="_blank" rel="noopener noreferrer" className="text-sm font-mono text-primary dark:text-primary-light hover:underline">
                        {apiBaseUrl}
                    </a>
                </>}
        </div>;
};

<RegionBaseUrl />

Real-time order book data with support for both one-time snapshots and continuous subscription updates. Each update contains changed price levels (asks and bids) as `[price, amount]` tuples. The server pushes incremental deltas every 100ms; if 10 seconds pass without an emitted `depth_update` on this subscription, a full snapshot is sent as a keepalive. Use this channel to maintain a live local order book for trading logic or display.

<WsChannelOverview operations={channelOperations} />

<WsAuthBadge required={channelMeta.authRequired} />

<Note>Connect to `wss://api.whitebit.com/ws` — see [WebSocket Overview](/websocket/overview) for protocol details and keepalive requirements.</Note>

## Rate limits

<WsRateLimits {...channelMeta.rateLimits} />

## Query order book depth (one-time)

Fetch a single order book snapshot without subscribing to updates.

<WsTupleTable fields={depthRequestParamsTupleFields} />

**Request**

<WsMessageExample data={exDepthRequest} />

**Response**

<WsMessageExample data={exDepthResponse} />

## Subscribe to order book depth

<WsTupleTable fields={depthSubscribeParamsTupleFields} />

<WsSubscribeSteps
  subscribe={exDepthSubscribe}
  confirmation={{id: 12, result: {status: "success"}, error: null}}
  update={exDepthFullReload}
  updateNote="The first depth_update is always a full snapshot — params[0] is true, past_update_id is absent. Initialize the local order book from this snapshot."
  updateExtra={
<div>
  <p style={{fontSize: '0.875rem', marginBottom: '0.75rem'}}>Subsequent <code>depth_update</code> messages contain only changed price levels — <code>params[0]</code> is <code>false</code> and <code>past_update_id</code> is present.</p>
  <WsMessageExample data={exDepthPartialUpdate} />
  <div style={{fontSize: '0.875rem', marginTop: '0.75rem', marginBottom: '0.5rem'}}>
    <span>Amount <code>"0"</code> → remove the price level. Amount non-zero → update or insert the price level.</span>
  </div>
  <div style={{borderLeft: '3px solid #60a5fa', paddingLeft: '0.75rem', marginTop: '0.75rem', fontSize: '0.8125rem', color: '#374151', lineHeight: '1.5'}}>
    If 10 seconds pass without an emitted <code>depth_update</code> on this subscription, the server pushes a full snapshot as a keepalive (<code>past_update_id</code> absent). Treat the message as a full reset.
  </div>
</div>
}
  unsubscribe={{id: 13, method: "depth_unsubscribe", params: ["BTC_USDT"]}}
  unsubscribeNote="Pass a market name in params to unsubscribe from that market only (`params[0]`: market name, e.g. `&#x22;BTC_USDT&#x22;`), or pass an empty array to unsubscribe from all markets."
/>

<Note>
  Public `depth` updates exclude Retail Price Improvement (RPI) orders. The stream includes only regular order book liquidity.
</Note>

## Including RPI orders in a local order book

Public `depth` excludes RPI orders by design, so they never appear in this stream. RPI resting liquidity is not published on any market-data feed — it is visible only in private active orders and in the exchange UI order book.

To display an account's own RPI orders alongside the public book, overlay its private active orders onto the local depth book. Subscribe to [`ordersPending`](/websocket/account-streams/orders-pending) (private) alongside `depth_subscribe`, then merge:

1. **Overlay only orders with `rpi: true`.** Non-RPI orders already appear in the public `depth` aggregate — overlaying every order double-counts them.
2. **Aggregate by price before merging.** Public `depth` levels are price-aggregated `[price, total amount]`, while the private stream is per-order. Sum the RPI orders by price level, and use each order's `left` (remaining amount), not `amount` (original size).
3. **Map the side.** Order `side` `1` (sell) → ask; `2` (buy) → bid.
4. **Upsert by `order_id`.** Add or update on `event_id` `1` (new) and `2` (update); remove on `3` (finish). `order_id` changes on modify — correlate with `client_order_id`. Stop-order activation emits a second `event_id=1` for the same `order_id`, so upsert rather than append.
5. **Filter by `market`** to match the depth subscription.

This overlay reflects only the account's own RPI orders. No feed exposes other participants' RPI liquidity.

## Update frequency

The server pushes updates every **100ms** when the order book changes within the subscribed depth. If 10 seconds pass without an emitted `depth_update` on this subscription, a full snapshot is sent as a keepalive.

## Error codes

<WsErrorCodes errorCodes={channelMeta.errorCodes} />

## Maintaining a local order book

To maintain an accurate local order book:

1. Subscribe and wait for the first message (`params[0]` is `true`) — initialize order book from snapshot
2. For each incremental update: if amount is `"0"` remove the price level; otherwise update or insert at the correct sorted position
3. Keep asks sorted ascending, bids sorted descending
4. Truncate to the configured limit after each update

## Update model

**First message** (`past_update_id` absent): Full order book snapshot. Replace any existing local state.

**Subsequent messages** (`past_update_id` present): Incremental deltas. Apply each price level change to the local book.

## Ordering guarantees

* Updates arrive in `update_id` order. Each incremental message's `past_update_id` matches the previous message's `update_id`.
* If 10 seconds pass without an emitted `depth_update` on this subscription, the server sends a full snapshot (`params[0]` is `true`, no `past_update_id`) as a keepalive. Treat this as a full reset.
* `update_id` reflects changes to the full order book for this market. A keepalive snapshot may carry an `update_id` greater than the last delta's `update_id` — this is expected and reflects book activity that did not produce a `depth_update` on this subscription, not a missed message.
* A gap between an incremental delta's `past_update_id` and the stored `update_id` indicates a missed message — re-subscribe to resync.

## Order book data object

<WsSchemaTable fields={depthUpdateData} />

<RelatedResources>
  * [Orderbook](/api-reference/market-data/orderbook) — full order book snapshot via REST for polling use cases.
  * [Depth](/api-reference/market-data/depth) — lightweight depth snapshot within ±2% of last price via REST.
</RelatedResources>

## Code examples

<CodeGroup>
  ```typescript TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  type IDepth = [string, string];

  interface OrderBook {
      asks: IDepth[];
      bids: IDepth[];
  }

  const ws = new WebSocket("wss://api.whitebit.com/ws");
  const orderBook: OrderBook = { asks: [], bids: [] };
  const LIMIT = 100;

  ws.addEventListener("open", () => {
      ws.send(
          JSON.stringify({
              id: 1,
              method: "depth_subscribe",
              params: ["ETH_BTC", LIMIT, "0", true]
          }),
      );
  });

  ws.addEventListener("message", (event: MessageEvent) => {
      const message = JSON.parse(event.data.toString());

      if (message.method === "depth_update") {
          const updateData = message.params[0] as Partial<OrderBook & { past_update_id?: number }>;
          const isFirstMessage = !updateData.past_update_id;

          if (isFirstMessage) {
              // First message or keepalive snapshot is a full snapshot - replace order book
              orderBook.asks = updateData.asks ?? [];
              orderBook.bids = updateData.bids ?? [];
          } else {
              // Subsequent messages are incremental updates
              applyUpdates(orderBook.asks, updateData.asks, "ask");
              applyUpdates(orderBook.bids, updateData.bids, "bid");
              truncateOrderBook(orderBook.asks);
              truncateOrderBook(orderBook.bids);
          }
      }
  });

  function applyUpdates(orderBookSide: IDepth[], updates: IDepth[] | undefined, side: "ask" | "bid") {
      if (updates === undefined) return;
      for (const [price, amount] of updates) {
          const priceIndex = orderBookSide.findIndex((level) => level[0] === price);

          if (amount === "0") {
              if (priceIndex !== -1) {
                  orderBookSide.splice(priceIndex, 1);
              }
          } else {
              if (priceIndex === -1) {
                 const insertIndex = orderBookSide.findIndex((level) =>
                      side === "ask" ? level[0] > price : level[0] < price
                  );

                  if (insertIndex === -1) {
                      orderBookSide.push([price, amount]);
                  } else {
                      orderBookSide.splice(insertIndex, 0, [price, amount]);
                  }
              } else {
                  orderBookSide[priceIndex][1] = amount;
              }
          }
      }
  }

  function truncateOrderBook(orderBookSide: IDepth[]) {
      if (orderBookSide.length > LIMIT) {
          orderBookSide.splice(LIMIT);
      }
  }
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
  import asyncio
  import json
  import websockets

  class OrderBook:
      def __init__(self):
          self.asks = []
          self.bids = []

  LIMIT = 100

  async def depth_subscribe():
      async with websockets.connect('wss://api.whitebit.com/ws') as ws:
          await ws.send(json.dumps({
              'id': 1,
              'method': 'depth_subscribe',
              'params': ['ETH_BTC', LIMIT, '0', True]
          }))

          async for message in ws:
              data = json.loads(message)
              if data.get('method') == 'depth_update':
                  handle_depth_update(data['params'])

  def handle_depth_update(params):
      update_data = params[0]
      is_first_message = update_data.get('past_update_id') is None

      if is_first_message:
          order_book.asks = update_data.get('asks', [])
          order_book.bids = update_data.get('bids', [])
      else:
          apply_updates(order_book.asks, update_data.get('asks', []), "ask")
          apply_updates(order_book.bids, update_data.get('bids', []), "bid")
          truncate_order_book(order_book.asks)
          truncate_order_book(order_book.bids)

  def apply_updates(order_book_side, updates, side):
      for price, amount in updates:
          price = str(price)
          amount = str(amount)

          price_index = next((i for i, level in enumerate(order_book_side) if level[0] == price), -1)

          if amount == '0':
              if price_index != -1:
                  order_book_side.pop(price_index)
          else:
              if price_index == -1:
                  insert_index = next((i for i, level in enumerate(order_book_side)
                                       if (side == "ask" and level[0] > price) or
                                       (side == "bid" and level[0] < price)), -1)
                  if insert_index == -1:
                      order_book_side.append([price, amount])
                  else:
                      order_book_side.insert(insert_index, [price, amount])
              else:
                  order_book_side[price_index][1] = amount

  def truncate_order_book(order_book_side):
      if len(order_book_side) > LIMIT:
          del order_book_side[LIMIT:]

  order_book = OrderBook()

  asyncio.get_event_loop().run_until_complete(depth_subscribe())
  ```

  ```java Java theme={"theme":{"light":"github-light","dark":"github-dark"}}
  import java.io.ByteArrayInputStream;
  import java.net.URI;
  import java.net.URISyntaxException;
  import java.nio.charset.StandardCharsets;
  import java.util.ArrayList;
  import java.util.List;
  import java.util.concurrent.CountDownLatch;
  import java.util.concurrent.TimeUnit;
  import javax.json.*;
  import javax.websocket.*;

  @ClientEndpoint
  public class OrderBookClient {
      private static final int LIMIT = 100;
      private static List<IDepth> asks = new ArrayList<>();
      private static List<IDepth> bids = new ArrayList<>();
      private static CountDownLatch latch;

      public static void main(String[] args) throws URISyntaxException, InterruptedException {
          latch = new CountDownLatch(1);
          WebSocketContainer container = ContainerProvider.getWebSocketContainer();
          URI uri = new URI("wss://api.whitebit.com/ws");
          container.connectToServer(OrderBookClient.class, uri);
          latch.await(1, TimeUnit.MINUTES);
      }

      @OnOpen
      public void onOpen(Session session) throws Exception {
          JsonObject message = Json.createObjectBuilder()
                  .add("id", 1)
                  .add("method", "depth_subscribe")
                  .add("params", Json.createArrayBuilder()
                          .add("ETH_BTC")
                          .add(LIMIT)
                          .add("0")
                          .add(true)
                  )
                  .build();
          session.getBasicRemote().sendText(message.toString());
      }

      @OnMessage
      public void onMessage(String message, Session session) {
          JsonReader reader = Json.createReader(new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8)));
          JsonObject jsonMessage = reader.readObject();

          if ("depth_update".equals(jsonMessage.getString("method"))) {
              JsonArray params = jsonMessage.getJsonArray("params");
              JsonObject updateData = params.getJsonObject(0);
              boolean isFirstMessage = !updateData.containsKey("past_update_id") ||
                                       updateData.isNull("past_update_id");

              if (isFirstMessage) {
                  asks = parseOrderBookSide(updateData.getJsonArray("asks"));
                  bids = parseOrderBookSide(updateData.getJsonArray("bids"));
              } else {
                  applyUpdates(asks, updateData.getJsonArray("asks"), "ask");
                  applyUpdates(bids, updateData.getJsonArray("bids"), "bid");
                  truncateOrderBook(asks);
                  truncateOrderBook(bids);
              }
          }
      }

      @OnClose
      public void onClose(Session session, CloseReason reason) {
          System.out.println("Connection closed: " + reason);
          latch.countDown();
      }

      @OnError
      public void onError(Session session, Throwable throwable) {
          throwable.printStackTrace();
          latch.countDown();
      }

      private static List<IDepth> parseOrderBookSide(JsonArray jsonArray) {
          List<IDepth> orderBookSide = new ArrayList<>();
          for (JsonValue value : jsonArray) {
              JsonArray level = value.asJsonArray();
              orderBookSide.add(new IDepth(level.getString(0), level.getString(1)));
          }
          return orderBookSide;
      }

      private static void applyUpdates(List<IDepth> orderBookSide, JsonArray updates, String side) {
          for (JsonValue value : updates) {
              JsonArray level = value.asJsonArray();
              String price = level.getString(0);
              String amount = level.getString(1);

              int priceIndex = -1;
              for (int i = 0; i < orderBookSide.size(); i++) {
                  if (orderBookSide.get(i).getPrice().equals(price)) {
                      priceIndex = i;
                      break;
                  }
              }

              if ("0".equals(amount)) {
                  if (priceIndex != -1) orderBookSide.remove(priceIndex);
              } else {
                  if (priceIndex == -1) {
                      int insertIndex = -1;
                      for (int i = 0; i < orderBookSide.size(); i++) {
                          if ((side.equals("ask") && orderBookSide.get(i).getPrice().compareTo(price) > 0) ||
                              (side.equals("bid") && orderBookSide.get(i).getPrice().compareTo(price) < 0)) {
                              insertIndex = i;
                              break;
                          }
                      }
                      if (insertIndex == -1) orderBookSide.add(new IDepth(price, amount));
                      else orderBookSide.add(insertIndex, new IDepth(price, amount));
                  } else {
                      orderBookSide.get(priceIndex).setAmount(amount);
                  }
              }
          }
      }

      private static void truncateOrderBook(List<IDepth> orderBookSide) {
          if (orderBookSide.size() > LIMIT) orderBookSide.subList(LIMIT, orderBookSide.size()).clear();
      }

      static class IDepth {
          private final String price;
          private String amount;

          public IDepth(String price, String amount) { this.price = price; this.amount = amount; }
          public String getPrice() { return price; }
          public String getAmount() { return amount; }
          public void setAmount(String amount) { this.amount = amount; }
      }
  }
  ```
</CodeGroup>
