Documentation
velho guide
velho is a TypeScript-first toolkit for authenticating with Twitch, calling Helix APIs, subscribing to EventSub, and chatting over WebSocket. This guide walks you through installation, core concepts, and integration patterns.
Getting started
Install velho in your project, set up Twitch credentials, and wire up the modules that match your use case. Each module is exportable on its own, so you can start small and scale capabilities over time.
Gather your Twitch credentials
Twitch issues the client_id, client_secret, and broadcaster_id values you will reference throughout this toolkit. Follow the official console steps so you have everything ready before coding:
- Open the Twitch Developer Console and register a new application (or reuse one you already manage).
- Provide an app name, select the target environment, and add a redirect URL you control. Save the configuration.
- From the app detail page, copy the Client ID, then click New Secret to generate a Client Secret. Store the secret securely because Twitch only shows it once.
-
Look up the Broadcaster ID (aka
broadcaster_idon Helix). You can:- Call
/helix/users?login=<channel>with your app token and read theidfield. - Run the Twitch CLI:
twitch api get users -q login=<channel>.
- Call
Drop the values into your environment file so the SDK and your deployment platform can read them:
TWITCH_CLIENT_ID=...
TWITCH_CLIENT_SECRET=...
TWITCH_BROADCASTER_ID=...
Include TWITCH_BROADCASTER_ID when you manage channel resources, subscribe to EventSub events, or call Helix endpoints that scope responses to a broadcaster.
Installation
Install from npm with your preferred package manager:
npm install velho
# or
pnpm add velho
# or
yarn add velho
Quick Setup with CLI Tool
Get started instantly with the interactive OAuth setup:
# Set your Twitch credentials
export TWITCH_CLIENT_ID="your_client_id"
export TWITCH_CLIENT_SECRET="your_client_secret"
# Run the interactive setup
npx velho-setup
This will automatically handle the OAuth flow and save credentials to your .env file.
Manual Setup
Alternatively, create a .env file containing your Twitch credentials:
TWITCH_CLIENT_ID=your_client_id
TWITCH_CLIENT_SECRET=your_client_secret
TWITCH_BROADCASTER_ID=your_broadcaster_id # optional for channel-scoped APIs
Quick start
The snippet below authenticates with Twitch and fetches the first stream using the Helix client. The auth helper automatically caches and refreshes tokens.
import { HelixClient, TwitchAuth } from "velho";
const auth = new TwitchAuth({
clientId: process.env.TWITCH_CLIENT_ID!,
clientSecret: process.env.TWITCH_CLIENT_SECRET!,
});
const helix = new HelixClient({
clientId: process.env.TWITCH_CLIENT_ID!,
auth,
});
const { data } = await helix.get("/streams", { query: { first: 1 } });
console.log(data);
By default, Helix requests use an app access token. Pass token to override the strategy for a specific request.
Authentication module
TwitchAuth provides complete OAuth 2.0 support including authorization code exchange, client credentials, and refresh token flows with built-in caching and refresh leeway.
import { TwitchAuth } from "velho";
const auth = new TwitchAuth({
clientId: "...",
clientSecret: "...",
refreshLeewaySeconds: 120,
});
// App access token (client credentials flow)
const appToken = await auth.getAppAccessToken(["channel:read:subscriptions"]);
// User access token from refresh token
const userToken = await auth.getUserAccessToken({ refreshToken: "..." });
// Exchange authorization code for tokens (OAuth 2.0 authorization code flow)
const newUserToken = await auth.exchangeAuthorizationCode({
code: "authorization_code_from_callback",
redirectUri: "http://localhost:3000/auth/callback",
});
- Complete OAuth 2.0 support: Authorization code exchange, client credentials, and refresh token flows
- Tokens cache per scope (app) or refresh token (user) until they are near expiry
forceRefreshbypasses the cache when a token is known to be invalid- Inject
fetchFnif your runtime lacks globalfetch(e.g. older Node versions or tests)
Helix client
A lightweight REST wrapper for Twitch Helix endpoints. Typed methods, Zod schema validation, automatic 401 retries, and rate-limit awareness ship out of the box.
import { HelixClient, HelixRequestError } from "velho";
import { z } from "zod";
const helix = new HelixClient({ clientId: "...", auth });
const channelsSchema = z.object({
data: z.array(
z.object({
broadcaster_id: z.string(),
broadcaster_name: z.string(),
broadcaster_language: z.string(),
})
),
});
try {
const response = await helix.get("/channels", {
query: { broadcaster_id: ["123456"] },
schema: channelsSchema,
});
console.log(response.data);
} catch (error) {
if (error instanceof HelixRequestError) {
console.error(error.status, error.message);
}
}
- Methods mirror HTTP verbs:
get,post,patch,put,delete. - Zod schemas enforce response shapes and surface parsing errors early.
- Rate-limit headers parse into
HelixRateLimitfor observability tooling.
Chat client
TwitchChatClient connects to Twitch IRC over WebSocket, handles PING/PONG, and emits strongly typed events for every message.
import { TwitchChatClient } from "velho";
const chat = new TwitchChatClient({
username: "my_bot",
token: process.env.TWITCH_CHAT_TOKEN!,
reconnect: true,
autoJoin: ["#velho"],
});
await chat.connect();
chat.on("ready", () => console.log("Chat ready"));
chat.on("message", (message) => {
console.log(`[${message.channel}] <${message.username}> ${message.text}`);
});
chat.say("#velho", "Hello Twitch!");
- Events include
ready,message,notice,join,part,reconnect,close, anderror. - Automatic reconnect with configurable delay; augment with custom backoff if necessary.
listenhelper subscribes to messages and returns an unsubscribe callback.
EventSub utilities
Choose webhook helpers or the WebSocket client depending on your delivery transport. Both approaches keep sessions healthy and verify Twitch signatures.
Webhook handler
import { handleEventSubWebhook } from "velho";
import { z } from "zod";
const cheerSchema = z.object({
broadcaster_user_id: z.string(),
is_anonymous: z.boolean(),
bits: z.number(),
});
export async function twitchWebhookHandler(req, res) {
const body = await getRawBody(req);
const result = await handleEventSubWebhook({
headers: req.headers,
body,
}, {
secret: process.env.TWITCH_EVENTSUB_SECRET!,
eventSchema: cheerSchema,
onNotification: async ({ subscription, event }) => {
console.log(subscription.type, event.bits);
},
onRevocation: ({ subscription }) => {
console.warn("Revoked:", subscription);
},
});
res.writeHead(result.status, result.headers);
res.end(result.body);
}
- Framework agnostic: provide raw headers and request body.
- Signature verification uses HMAC-SHA256 with constant-time comparison.
- Automatic response to
webhook_callback_verificationchallenges.
WebSocket client
import { EventSubWebSocketClient } from "velho";
const wsClient = new EventSubWebSocketClient({ autoReconnect: true });
await wsClient.connect();
wsClient.on("connected", (session) => {
console.log("Session ID", session.id);
});
wsClient.on("notification", ({ subscription, event }) => {
console.log(subscription.type, event);
});
- Keeps sessions alive and honors Twitch reconnect instructions.
- Resets keepalive timers to detect silent disconnects.
- Pair with the Helix client to create subscriptions using the active session ID.
Design & DX highlights
- Type safety everywhere — Validate responses at runtime while benefiting from rich TypeScript definitions.
- Dual builds — ESM and CJS bundles with source maps and types ship by default.
- Extensible fetch — Swap in custom
fetchimplementations for observability, testing, or alternative runtimes. - Node 18+ — Targets modern runtimes with native
fetch, Web Streams, and WebSocket.
FAQ
Does velho handle OAuth authorization code flows?
Yes! velho now includes complete OAuth 2.0 support including authorization code exchange. Use the exchangeAuthorizationCode() method or the interactive velho-setup CLI tool for a seamless setup experience.
Can I use velho outside of Node.js?
The toolkit targets Node 18+ but allows custom fetchFn so you can integrate with environments like Cloudflare Workers or instrumented undici instances with minimal changes.
How do I report issues or contribute?
Open issues and pull requests on GitHub. Review the contribution guidelines in CONTRIBUTING.md before submitting changes.