npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

what-up-sdk

v0.0.2

Published

Single-session WhatsApp host SDK — Baileys socket lifecycle, provisioning, resilient reconnect, normalized inbound, and per-session send behind one object. One process drives one number.

Readme

what-up-sdk

Single-session WhatsApp host SDK. One process drives one number: socket lifecycle, provisioning (QR + pairing code), resilient reconnect, normalized inbound, and per-session send — behind one Session object, with no "brain" logic.

Why one process per number? WhatsApp allows exactly one live linked-device connection per number — two processes on the same session collide (conflict: replaced, disconnect code 440). One-process-per-number makes that invariant structural and gives each number crash/CPU isolation.

The same package also ships official-API transports — WhatsApp Business (Cloud API) and Twilio — that share the SDK's InboundRouter, so one routing layer works across all three.

Install

npm install what-up-sdk baileys

baileys is a peer dependency — you own its version (currently 7.0.0-rc13). The Cloud API and Twilio surfaces don't need it at runtime, but your package manager will still want the peer satisfied.

⚠️ The Session surface drives WhatsApp through Baileys, an unofficial WhatsApp Web client. Numbers can be banned; the SDK ships ban-risk detection (onBanRisk), rate limiting, and human-like send pacing, but use it at your own risk. The Cloud API and Twilio transports are official channels with no ban risk.

Usage

import { Session, FileAuthStore } from 'what-up-sdk'
import P from 'pino'

const session = new Session('my-bot', {
  authStore: new FileAuthStore('./auth'),
  logger: P({ level: 'info' }),
  // Outbound caps (host concern — the brain no longer tracks this):
  rateLimit: { perRecipientPerHour: 30 }, // over → send throws RateLimitError
  // Send queue: serialized, paced, offline-buffered. Sending is the priority path.
  sendQueue: {
    minDelayMs: 1000,   // human-like spacing (jittered up to maxDelayMs)
    maxDelayMs: 3000,
    maxConcurrent: 1,   // serialize — never fire a burst at WhatsApp
    ttlMs: 60_000,      // a send waits up to 60s for reconnect, then rejects
  },
})

session.onQR((qr) => console.log('scan this:', qr))
session.onStatus((s) => console.log('status:', s))

// Ban-risk warnings (on by default). Fires on rapid reconnects, disconnect
// churn, frequent QR re-scans, high send-error rate, rate limits, or a reachout
// timelock. Wire it to your alerting / pause the session on 'critical'.
session.onBanRisk((e) => {
  console.warn(`[${e.riskLevel}] ${e.signals.join(', ')} — ${e.recommendation}`)
})

session.onMessage(async (m) => {
  // m is a normalized InboundMessage — ephemeral/view-once already unwrapped,
  // sender resolved into both LID and phone-number forms, mentions + reply-to-self
  // resolved against the hosted number's identity. Groups AND DMs are delivered;
  // the SDK applies no chat-type filter — that policy is yours.
  const forBot = m.isGroup ? m.mentionsMe || m.isReplyToSelf : true
  if (!forBot) return

  // Peek the rate limit BEFORE expensive work (e.g. an LLM call) to avoid waste.
  if (!session.rateLimit.check(m.chatJid)) return // over cap — drop silently

  await session.sendText(m.chatJid, `You said: ${m.text}`, { quoted: m.raw })
})

await session.connect()

Route one number by incoming phone number

One number, different responses per caller: InboundRouter is a per-sender gateway over the number's single inbound stream. String patterns match the sender's phone-number form digit-wise (exact numbers or 27*-style prefixes); regexes test the phone digits; predicates see the full sender surface. Precedence is fixed — exact → longest prefix → regex/predicate (registration order) → fallback:

import { InboundRouter } from 'what-up-sdk'

const router = new InboundRouter()
  .route('+27 82 123 4567', (m) => session.sendText(m.chatJid, 'concierge 🥂'))
  .route(['14155550100', '14155550101'], handleOpsAllowlist) // one handler, many numbers
  .route('27*', (m) => session.sendText(m.chatJid, 'SA desk 🇿🇦'))
  .route(/^1\d{10}$/, handleNorthAmerica)
  .fallback((m) => session.sendText(m.chatJid, 'main line'))

router.attach(session) // same router attaches to a HostClient for hosted numbers

router.resolve(m) peeks at which route would win without invoking it, and router.dispatch(m) routes one message by hand.

Same router, official API: the Cloud API intake

For the one-number/many-users surface specifically, the WhatsApp Business (Cloud API) is often the better transport: it's Meta's official channel, so there is no ban risk, no QR pairing, no reconnect/lease machinery — inbound arrives on a webhook and outbound goes through the Graph API. The trade-offs are structural: 1:1 business messaging only (no groups), and free-form replies are allowed only within 24h of the user's last message (outside it you must send an approved template).

CloudApiIntake is the webhook half — framework-free (feed it the GET handshake and each raw POST body); it verifies X-Hub-Signature-256 and emits CloudInboundMessages that satisfy RoutableMessage, so the same InboundRouter attaches unchanged. CloudApiNumber is the send half, shaped like Session's send surface:

import { CloudApiIntake, CloudApiNumber, InboundRouter, type CloudInboundMessage } from 'what-up-sdk'

const intake = new CloudApiIntake({ verifyToken, appSecret })
const number = new CloudApiNumber({ accessToken, phoneNumberId })

new InboundRouter<CloudInboundMessage>()
  .route('+27 82 123 4567', (m) => number.sendText(m.chatJid, 'concierge 🥂', { quotedId: m.id! }))
  .route('27*', (m) => number.sendText(m.chatJid, 'SA desk 🇿🇦'))
  .fallback((m) => number.sendText(m.chatJid, 'main line'))
  .attach(intake)

// In your HTTP server:
//   GET  /webhook → intake.handshake(searchParams) → echo the challenge (or 403)
//   POST /webhook → intake.ingest(rawBody, req.headers['x-hub-signature-256']) → always 200

On Twilio instead? If your WhatsApp number is hosted through Twilio's Business API integration, the same surface exists as TwilioIntake + TwilioNumber — Twilio's dialect differs (form-encoded webhooks signed with X-Twilio-Signature over the URL+params, REST sends with whatsapp:+… addressing, templates via Content SIDs, window violations as error 63016), but TwilioInboundMessage satisfies RoutableMessage too, so the router attaches identically.

Provisioning

Provision from a browser (mount in any HTTP server):

import { renderProvisioningPage } from 'what-up-sdk'
// GET /qr  →  res.end(await renderProvisioningPage(session))

Or programmatically, with a pairing code instead of a QR:

await session.connect()
const code = await session.requestPairingCode('27821234567') // → "ABCD-1234"

connect() returns once the socket is wired, not once it is online — but requestPairingCode() awaits socket readiness itself, so calling it straight after connect() is safe. If the request fails, it rolls back the partial credentials Baileys writes up front, so a failed attempt can't poison the next connect.

Pairing-code linking is sensitive to the browser option: WhatsApp shows browser[0] to the operator as the linking device, and an unrecognized value there can fail the link. It defaults to Browsers.macOS('Chrome') — override it with a Browsers.* helper rather than a made-up tuple.

Surface

| Member | Purpose | |---|---| | new Session(id, { authStore, ... }) | one number, one socket | | connect() / logout() | lifecycle; logout() clears auth | | onMessage / onQR / onStatus / onBanRisk | subscriptions (multiple allowed) | | banRisk | the BanRiskDetector — check() on demand, or tune via the banRisk option | | requestPairingCode(phone) | code pairing (no QR) | | sendText / sendImage / sendFile | per-session send → queued, paced, cap-checked, SessionError-retried; opts.priority jumps the queue | | sendQueue | the SendQueue — serialization, pacing, priority, offline buffer + flush on reconnect, backpressure | | rateLimit | the RateLimiter — check(jid) to peek; per-recipient/per-session caps enforced on every send | | InboundRouter | per-sender gateway: route one number's inbound to different handlers by incoming phone number (exact / prefix / regex / predicate / fallback); attach() to a Session or HostClient | | CloudApiIntake | WhatsApp Business (Cloud API) webhook intake: GET handshake, X-Hub-Signature-256 verification, normalized CloudInboundMessage fan-out; router-attachable | | CloudApiNumber | Cloud API send handle (Graph API): sendText / sendImageUrl / sendTemplate / markRead / downloadMedia; surfaces the 24h-window rejection as CloudApiError code 131047 | | TwilioIntake | Twilio WhatsApp webhook intake: X-Twilio-Signature verification, form-param normalization to TwilioInboundMessage, status callbacks via onStatus; router-attachable | | TwilioNumber | Twilio send handle (Messages REST API): sendText / sendMediaUrl / sendContent (Content templates) / downloadMedia; 24h-window rejection surfaces as TwilioError code 63016 | | sendPresence / updateProfileName | presence + profile | | listGroups() | { jid, subject }[] | | downloadMedia(m) / downloadMediaRaw(key, content) | media, rebuilds expired keys | | getSocket() | escape hatch to the raw Baileys socket |

Subpath exports

| Import | What it is | |---|---| | what-up-sdk | the full surface: Session, InboundRouter, Cloud API + Twilio transports, auth stores | | what-up-sdk/host | HostClient — consume a number hosted by a what-up control plane | | what-up-sdk/cloud-api | Cloud API transport only | | what-up-sdk/twilio | Twilio transport only | | what-up-sdk/webhook | webhook intake primitives |

More

Runnable examples, the hosting control planes, and the architecture diagram live in the monorepo: github.com/edumame/what-up-sdk.

License

MIT — extracted from the MentorMates bot's transport layer and hardened with patterns from WaSP (MIT).