
Security News
Axios Maintainer Confirms Social Engineering Attack Behind npm Compromise
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.
TypeScript-first toolkit for building Twitch integrations. velho bundles authentication helpers, a typed Helix REST client, Twitch chat (IRC over WebSocket), and EventSub utilities—all guarded by Zod validation and shipped as dual ESM/CJS builds.
velho-setup) that handles the entire authorization flow and saves credentials automatically.GET/POST helpers, automatic retry on 401, and rate-limit header parsing.PING, and auto-reconnect to Twitch chat..d.ts, exports map, sourcemaps, and tsup bundling for Node 18+.Check out the example files to see velho in action:
For a complete getting started guide, see GETTING_STARTED.md.
fetch, Web Streams, and global WebSocket compatibility).Follow these Twitch developer console steps to gather the values velho expects:
Website Integration works for most use cases), and add an OAuth redirect URL you control. Save.broadcaster_id). Either:
curl -H "Client-ID: <client-id>" -H "Authorization: Bearer <token>" "https://api.twitch.tv/helix/users?login=<channel-name>" and copy the id field, ortwitch api get users -q login=<channel-name> orKeep these values in your .env file:
TWITCH_CLIENT_ID=...
TWITCH_CLIENT_SECRET=...
TWITCH_BROADCASTER_ID=...
TWITCH_BROADCASTER_ID is optional unless you call Helix endpoints that require it (e.g. channel management) or set up EventSub subscriptions tied to a broadcaster.
npm install velho
# or
pnpm add velho
# or
yarn add velho
Get started instantly with the interactive OAuth setup:
# Create .env file with your Twitch credentials
echo "TWITCH_CLIENT_ID=your_client_id" > .env
echo "TWITCH_CLIENT_SECRET=your_client_secret" >> .env
# Run the interactive setup
npx velho-setup
This will:
TWITCH_BOT_REFRESH_TOKEN and TWITCH_BOT_USERNAME to your .env fileimport { 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);
Create a .env file in your project root with your Twitch credentials:
# .env
TWITCH_CLIENT_ID=your_client_id_here
TWITCH_CLIENT_SECRET=your_client_secret_here
TWITCH_BROADCASTER_ID=your_broadcaster_id_here # optional for channel-scoped APIs
Get these credentials from the Twitch Developer Console.
The authentication helper caches tokens per scope set. The Helix client retries once on 401 by invalidating and refreshing the token automatically.
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",
});
Key points:
forceRefresh bypasses the cache when you know the token is invalidfetchFn if your runtime does not expose global fetch (e.g., older Node versions or testing environments)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.data[0]);
console.log("Remaining quota:", response.rateLimit.remaining);
} catch (error) {
if (error instanceof HelixRequestError) {
console.error(error.status, error.details);
}
}
Highlights:
get, post, patch, put, delete.schema (optional) enforces response shape with Zod.token overrides the default token strategy per request (app vs user).ratelimit-*) are parsed into HelixRateLimit on every response.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!");
Capabilities:
ready, message, notice, join, part, reconnect, close, raw, error.PING/PONG automatically.listen(listener) convenience subscribes to messages and returns an unsubscribe callback.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);
}
webhook_callback_verification support returns the challenge automatically.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);
});
dist/index.mjs) and CommonJS (dist/index.cjs) with exports map and .d.ts files.fetch for testing or alternative runtimes (e.g., undici with instrumentation).# Install dependencies
git clone <repo>
cd velho
npm install
# Type-check
npm run check
# Build (ESM+CJS+d.ts)
npm run build
src/.dist/ via tsup and cleans the folder before each run.tests/.We welcome contributions! See CONTRIBUTING.md for the full workflow, coding standards, and release process.
MIT © 2024 Joni Juntto
FAQs
TypeScript-first Twitch connectivity toolkit
We found that velho demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.

Security News
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.