Quickstart

Add nyasa to any page or interactive flow in under 5 minutes.

Install

npm install @devanshhq/nyasa

ESM (bundler)

The most common integration path. Pass any container element or CSS selector, and nyasa starts collecting immediately.

import { collect } from '@devanshhq/nyasa'

const handle = collect('#signup-container', {
  endpoint: 'https://your-api.example.com/score',
  sessionId: crypto.randomUUID(),
})

collect accepts any HTMLElement: a <form>, a <div> wrapping a multi-step wizard, a <section> containing a login flow, or the document.body itself. Behavioral collectors that scope to the container (keystroke, paste, corrections, field timing) will listen within that element. Mouse, scroll, click, and session rhythm collectors always operate at the document level regardless of what element you pass.

collect sets up two automatic flush triggers:

  • Form submit: if the container element is or contains a <form>, the SDK flushes when that form submits.
  • Tab close or backgrounding: the SDK catches the visibilitychange event and flushes then too, so you get data even when users leave mid-session.

For flows without a form submit (multi-step wizards, login buttons, custom flows), use flush() directly.

Script tag (IIFE)

If you are not using a bundler, load the prebuilt global bundle instead.

<script src="https://cdn.example.com/@devanshhq/nyasa/index.global.js"></script>
<script>
  BehaviorSDK.collect('#app-container', {
    endpoint: 'https://your-api.example.com/score',
    sessionId: crypto.randomUUID(),
  })
</script>

The global name is BehaviorSDK. Everything else behaves identically to the ESM build.

Manual flush (SPAs and custom flows)

When there is no traditional form submit, call flush() at the moment you want to score the session: after a button click, at the end of a wizard step, or before a route change.

import { collect } from '@devanshhq/nyasa'

const handle = collect('#checkout-container', {
  endpoint: '/api/score',
  sessionId: crypto.randomUUID(),
})

// Score at your chosen moment:
async function onContinue() {
  handle.flush()       // sends payload via sendBeacon, detaches collectors
  await nextStep()
}

// Clean up without scoring (e.g. user navigated away):
function onExit() {
  handle.stop()        // detaches collectors, does NOT send
}

Both flush() and stop() are idempotent. Calling either more than once does nothing.

React hook pattern

Use BehaviorScanner directly when you need control inside a component lifecycle. The container element can be any element in your tree.

import { useEffect, useRef } from 'react'
import { BehaviorScanner } from '@devanshhq/nyasa'

function useNyasa(sessionId: string) {
  const containerRef = useRef<HTMLElement>(null)
  const scannerRef = useRef<BehaviorScanner | null>(null)

  useEffect(() => {
    const el = containerRef.current
    if (!el) return

    const scanner = new BehaviorScanner().attach(el)
    scannerRef.current = scanner

    return () => scanner.detach()
  }, [sessionId])

  function getPayload() {
    return scannerRef.current?.buildPayload(sessionId) ?? null
  }

  return { containerRef, getPayload }
}

Verifying the integration

Open your browser's Network tab and filter for requests to your endpoint. After your flush point (form submit, button click, or tab close), you should see a sendBeacon POST with a JSON body. The payload will contain a detections object and a verdict field.

sendBeacon requests appear under the "Ping" type in Chrome DevTools. If you do not see it, filter by your endpoint URL directly.

What to do with the payload

Forward the payload body to the Zoven scoring API. The API will return an actor type and risk score. For the exact payload shape, see Payload.