Skip to content

Quickstart

Install WriteTrack, attach it to a text input, and send captured typing data to your server.

Terminal window
npm i writetrack

Attach WriteTrack to any <textarea>, <input>, or contenteditable element:

import { WriteTrack, webhook } from 'writetrack';
const textarea =
document.querySelector<HTMLTextAreaElement>('#response-field')!;
const tracker = new WriteTrack({ target: textarea });
// Session data is POSTed to your endpoint when getData() is called
tracker.pipe(webhook({ url: 'https://api.example.com/writetrack' }));
// Start recording before the user begins typing
tracker.start();

Call getData() on form submit — registered sinks deliver the data automatically:

document.getElementById('response-form')!.addEventListener('submit', (e) => {
e.preventDefault();
// getData() dispatches to all registered sinks
const data = tracker.getData();
tracker.stop();
});

WriteTrack also ships sinks for Datadog, Segment, and OpenTelemetry. See Output Sinks for details.

getData() returns a structured object with the full session:

{
"version": "2.1.0",
"metadata": {
"tool": { "name": "writetrack", "version": "0.7.0" },
"targetElement": "textarea",
"fieldId": "response-field",
"sessionId": "wt_a1b2c3d4e5f6",
"timestamp": "2026-02-15T12:00:00.000Z",
"duration": 8432
},
"session": {
"events": [{ "type": "keydown", "key": "H", "timestamp": 1012.5 }],
"clipboardEvents": [],
"undoRedoEvents": [],
"selectionEvents": [],
"programmaticInsertionEvents": [],
"compositionEvents": [],
"rawText": "Hello world",
"sessionStartTime": 1000.0,
"sessionEndTime": 9432.0
},
"quality": {
"overallScore": 0.85,
"completeness": 0.92,
"sequenceValid": true,
"timingValid": true,
"sessionDuration": 8432,
"eventCount": 142,
"qualityLevel": "GOOD",
"issues": [],
"validatedAt": "2026-02-15T12:00:08.432Z"
}
}

The quality.qualityLevel field (POOR, FAIR, GOOD, EXCELLENT) reflects whether enough behavioral data was captured for meaningful analysis. Short sessions or sessions with few keystrokes will score lower.

A complete, self-contained page using the browser bundle:

<form id="response-form">
<textarea
id="response-field"
rows="10"
placeholder="Type your response..."
></textarea>
<button type="submit">Submit</button>
</form>
<script type="module">
import { WriteTrack } from 'https://unpkg.com/writetrack/dist/browser/index.js';
import { webhook } from 'https://unpkg.com/writetrack/dist/browser/pipes.js';
const textarea = document.getElementById('response-field');
const tracker = new WriteTrack({ target: textarea });
tracker.pipe(webhook({ url: '/api/writetrack' }));
tracker.start();
document.getElementById('response-form').addEventListener('submit', (e) => {
e.preventDefault();
tracker.getData(); // dispatches to webhook
tracker.stop();
});
</script>

Always stop the tracker when you’re done to remove event listeners:

tracker.stop();

Tag sessions with identifiers that flow through to getData() output and all sinks:

const tracker = new WriteTrack({
target: textarea,
userId: 'u_abc123',
contentId: 'post_draft_42',
metadata: { formName: 'signup', variant: 'B' },
});
FieldTypeDescription
userIdstringWho is typing. Appears in metadata.userId.
contentIdstringWhat they’re typing into. Appears in metadata.contentId.
metadataRecord<string, unknown>Arbitrary key-value pairs. Appears as metadata.custom.

If you have a license key (or are running on localhost), you can analyze the captured session for authenticity signals:

import { formatIndicator } from 'writetrack';
const analysis = await tracker.getAnalysis();
if (analysis) {
console.log(formatIndicator(analysis.contentOrigin.indicator));
console.log(formatIndicator(analysis.timingAuthenticity.indicator));
}

Or combine capture data with analysis in one call:

const report = await tracker.getSessionReport();
// report.data — captured events (same as getData())
// report.analysis — authenticity analysis (same as getAnalysis())

See the Analysis guide for full details.