ग्राहक · developer docs
Add Grahak to your product
Grahak is a support widget you drop into your app. Your users are already signed in with you, so you vouch for them: your backend mints a short-lived token, and the widget trades it for a session. You never build a chat UI or run a server for messages.
How it fits together
Two pieces of code get you live: an endpoint on your backend that returns a signed token for the logged-in user, and one call (or one provider) on your frontend that boots the widget.
Your backend
signs a token
Your frontend
boots the widget
Grahak
opens the session
Install the SDK
Plain JavaScript works in any web app, whatever your framework. If you build in React, the wrapper keeps the widget mounted for you. Pick one:
npm install @grahak/jsMint an identity token
This is the only backend work. You sign a token for the logged-in user with an RSA key. Grahak stores only the public half, like an SSH key, and verifies every token against it.
Generate a keypair
# Private key — keep this secret, on your server only
openssl genpkey -algorithm RSA -pkcs8 -out grahak-private.pem -pkeyopt rsa_keygen_bits:2048
# Public key — paste this into Console, Identity keys
openssl pkey -in grahak-private.pem -pubout -out grahak-public.pemAdd the public key
In Console, open your project, go to Identity keys and paste the contents of grahak-public.pem. Keep grahak-private.pem on your server as a secret, for example GRAHAK_PRIVATE_KEY. You can add and rotate multiple keys.
Sign a token for each user
The token carries a few claims. Everything else is optional display data.
audsubexpemail, name, avatar_urlmetadata// GET /api/me/grahak-identity (Next.js route handler)
import { SignJWT, importPKCS8 } from "jose";
const PROJECT_ID = process.env.GRAHAK_PROJECT_ID!;
const PRIVATE_KEY = process.env.GRAHAK_PRIVATE_KEY!; // PKCS#8 PEM
export async function GET() {
const user = await getCurrentUser(); // your existing auth
const key = await importPKCS8(PRIVATE_KEY, "RS256");
const jwt = await new SignJWT({
email: user.email,
name: user.name,
avatar_url: user.avatarUrl,
})
.setProtectedHeader({ alg: "RS256" })
.setAudience(PROJECT_ID) // aud = your project ID
.setSubject(user.id) // sub = your stable user ID
.setIssuedAt()
.setExpirationTime("5m") // short-lived; tokens are single-use
.sign(key);
return Response.json({ jwt });
}GET /api/me/grahak-identity returning { jwt }. Mint a fresh token each time the widget connects: tokens are single-use, so a replayed one is rejected.Embed the widget
Point the widget at that endpoint and mount it. Pass userJwt as a getter, not a string, so each reconnect (a reload, a re-login) fetches a fresh token.
import { init } from "@grahak/js";
const widget = init({
projectId: "YOUR_PROJECT_ID",
// A getter, not a string: tokens are single-use, so each
// (re)connect fetches a fresh one.
userJwt: async () => {
const res = await fetch("/api/me/grahak-identity");
const { jwt } = await res.json();
return jwt;
},
});
widget.mountOverlay(); // floating launcher, bottom-rightmountOverlay() adds a floating launcher in the corner and manages the panel for you. Want it inline instead of floating? See mode: "attach" in the reference.Anonymous visitors
For logged-out pages, a docs site or a landing page, skip token minting and let visitors talk to you anonymously.
import { init } from "@grahak/js";
// No token: visitors talk to you anonymously.
init({ projectId: "YOUR_PROJECT_ID", anonymous: true }).mountOverlay();Running both? Pass userJwt and anonymous together: signed-in visitors are identified, everyone else stays anonymous. Grahak remembers an anonymous visitor in their browser, so a returning one keeps the same thread.
Controlling the widget
Drive it from your own UI: your own button, an unread badge, a theme that follows your app.
- Open and close. open(), close(), toggle() — or the same from useGrahak() in React.
- Unread count. Subscribe to the unread event (or useGrahakUnread()) to badge your own button.
- Theme. setTheme("dark" | "light"), or the theme prop, to match your app.
- Logout. Call destroy() when your user signs out. In React it happens on unmount.
const widget = init({ projectId, userJwt })
.mountOverlay({ launcher: false }); // I'll use my own button
myButton.addEventListener("click", () => widget.toggle());
// Badge my own button with the unread count.
widget.on("unread", ({ count }) => renderBadge(count));
// Follow my app's theme, and tear down on logout.
widget.setTheme("dark");
onLogout(() => widget.destroy());Bring your own button
Prefer not to wire the events yourself? Pass your own element to launcher. Grahak binds its click to toggle() and reflects state onto it, so plain CSS drives the look: it sets data-grahak-open (and aria-expanded) and data-grahak-unread. Your element stays where it is in your DOM — you own its layout.
const myButton = document.querySelector("#support");
init({ projectId, userJwt }).mountOverlay({ launcher: myButton });
// Grahak keeps these in sync on your element — no event wiring needed:
// data-grahak-open="true | false" (also sets aria-expanded)
// data-grahak-unread="true | false" (true only while closed with unread)#support[data-grahak-open="true"] .chat { display: none }
#support[data-grahak-open="true"] .close { display: block }
#support[data-grahak-unread="true"] .dot { display: block }API reference
Options — init(options) and <GrahakProvider>
projectIduserJwtanonymousthememodelauncherOverlay — mountOverlay(options) and the overlay prop
positionlauncheroffsetwidth, heightaccentColorzIndexEvents — widget.on(event, handler)
readyopen, closeunreadresizeerror