Web USB API Integration

Our company is engaged in the development, support and maintenance of sites of any complexity. From simple one-page sites to large-scale cluster systems built on micro services. Experience of developers is confirmed by certificates from vendors.
Development and maintenance of all types of websites:
Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:
Development stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1215
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1043
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    815

Web USB API Implementation on Website

WebUSB API opens access to USB devices directly from the browser without drivers or native applications. Label printers, POS terminals, industrial scanners, Arduino devices, microcontroller programmers — all connect to a web interface directly.

Limitations and Reality

Support: Chrome/Edge 61+ (desktop and Android). Safari and Firefox — not supported.

HTTPS only. User gesture only. USB devices with active OS USB drivers (HID, mass storage, system driver printers) — not available via WebUSB: browser doesn't intercept them from OS. Need special firmware with WebUSB descriptors or device without auto-installed drivers.

Request and Connect Device

interface USBDeviceInfo {
  vendorId: number   // from manufacturer documentation
  productId: number
}

async function requestUSBDevice(filters: USBDeviceInfo[]): Promise<USBDevice> {
  const device = await navigator.usb.requestDevice({ filters })
  return device
}

async function connectDevice(device: USBDevice): Promise<void> {
  await device.open()

  // If device has multiple configurations — select needed one
  if (device.configuration === null) {
    await device.selectConfiguration(1)
  }

  // Claim interface (number from documentation or USB descriptor)
  await device.claimInterface(0)

  console.log(`Connected: ${device.manufacturerName} ${device.productName}`)
  console.log(`USB ${device.usbVersionMajor}.${device.usbVersionMinor}`)
}

Label Printer ZPL (Zebra/analogues)

Zebra printers use ZPL (Zebra Programming Language). Commands sent as plain text via bulk transfer:

class ZebraPrinter {
  private device: USBDevice
  private interfaceNumber = 0
  private endpointOut = 1  // bulk OUT endpoint — from USB descriptor

  constructor(device: USBDevice) {
    this.device = device
  }

  async print(zplCommands: string): Promise<void> {
    const encoder = new TextEncoder()
    const data = encoder.encode(zplCommands)

    const result = await this.device.transferOut(this.endpointOut, data)
    if (result.status !== 'ok') {
      throw new Error(`Print error: ${result.status}`)
    }
  }

  async printLabel(params: {
    barcode: string
    title: string
    price: string
    sku: string
  }): Promise<void> {
    // ZPL layout for 60x40mm label
    const zpl = `
^XA
^CI28
^FO20,10^A0N,24,24^FD${params.title}^FS
^FO20,40^BY2^BCN,50,Y,N,N^FD${params.barcode}^FS
^FO20,100^A0N,20,20^FDSKU: ${params.sku}^FS
^FO20,125^A0N,28,28^FD${params.price}^FS
^XZ
    `.trim()

    await this.print(zpl)
  }

  async getStatus(): Promise<string> {
    // Host Status Command
    await this.print('~HS')

    // Read response from bulk IN endpoint
    const result = await this.device.transferIn(1, 64)  // endpoint IN, 64 bytes
    const decoder = new TextDecoder()
    return decoder.decode(result.data!)
  }
}

Arduino: Bidirectional Data Exchange

Arduino with CDC firmware acts as USB Serial. But without CDC OS driver — custom approach via Arduino WebUSB library:

class ArduinoDevice {
  private device: USBDevice
  private interfaceNumber = 2
  private endpointIn = 5
  private endpointOut = 4
  private decoder = new TextDecoder()
  private encoder = new TextEncoder()

  private readBuffer = ''
  private isReading = false

  constructor(device: USBDevice) {
    this.device = device
  }

  async startReading(onData: (line: string) => void) {
    this.isReading = true

    while (this.isReading) {
      try {
        const result = await this.device.transferIn(this.endpointIn, 64)
        const chunk = this.decoder.decode(result.data!, { stream: true })
        this.readBuffer += chunk

        const lines = this.readBuffer.split('\n')
        this.readBuffer = lines.pop()!  // Last element — incomplete line

        for (const line of lines) {
          if (line.trim()) onData(line.trim())
        }
      } catch (err) {
        if ((err as Error).name === 'NetworkError') break  // Device disconnected
        throw err
      }
    }
  }

  async sendCommand(command: string): Promise<void> {
    const data = this.encoder.encode(command + '\n')
    await this.device.transferOut(this.endpointOut, data)
  }

  stopReading() {
    this.isReading = false
  }
}

// Usage
const arduino = new ArduinoDevice(device)

arduino.startReading((line) => {
  // Parse sensor data: "TEMP:23.5,HUM:65.2"
  const match = line.match(/TEMP:([\d.]+),HUM:([\d.]+)/)
  if (match) {
    setSensorData({ temp: parseFloat(match[1]), humidity: parseFloat(match[2]) })
  }
})

await arduino.sendCommand('LED:ON')
await arduino.sendCommand('SERVO:90')

React Hook for WebUSB

function useWebUSB() {
  const [device, setDevice] = useState<USBDevice | null>(null)
  const [isConnected, setIsConnected] = useState(false)
  const [isSupported] = useState(() => 'usb' in navigator)

  useEffect(() => {
    if (!isSupported) return

    function onConnect(event: USBConnectionEvent) {
      console.log('USB connected:', event.device.productName)
    }

    function onDisconnect(event: USBConnectionEvent) {
      if (event.device === device) {
        setDevice(null)
        setIsConnected(false)
      }
    }

    navigator.usb.addEventListener('connect', onConnect)
    navigator.usb.addEventListener('disconnect', onDisconnect)

    return () => {
      navigator.usb.removeEventListener('connect', onConnect)
      navigator.usb.removeEventListener('disconnect', onDisconnect)
    }
  }, [device, isSupported])

  // Reconnect to previously authorized devices (without dialog)
  async function reconnectPaired() {
    const devices = await navigator.usb.getDevices()
    if (devices.length > 0) {
      const dev = devices[0]
      await connectDevice(dev)
      setDevice(dev)
      setIsConnected(true)
    }
  }

  return { device, isConnected, isSupported, reconnectPaired }
}

Reading USB Descriptor

When documentation is missing — read device descriptors directly:

function inspectDevice(device: USBDevice) {
  console.log('Vendor ID:', device.vendorId.toString(16))
  console.log('Product ID:', device.productId.toString(16))

  device.configuration?.interfaces.forEach((iface) => {
    console.log(`\nInterface ${iface.interfaceNumber}:`)
    iface.alternates.forEach((alt) => {
      alt.endpoints.forEach((ep) => {
        console.log(`  Endpoint ${ep.endpointNumber}: ${ep.direction} ${ep.type}, packet size: ${ep.packetSize}`)
      })
    })
  })
}

This allows finding needed endpoint numbers without documentation.

What We Do

Get Vendor ID and Product ID, study device protocol documentation or reverse-engineer it. Implement connection, sending commands and receiving data, handling disconnection, connection status UI. Test on target OS (Windows requires special attention with WinUSB drivers).

Timeline: integration with documented USB device — 3–5 days. Protocol reverse-engineering — 8–14 days.