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

teiwah

v0.1.10

Published

Type-safe server-side SDK for the Teiwah WhatsApp messaging API.

Readme

Teiwah TypeScript SDK

Type-safe, server-side access to WhatsApp messaging through the Teiwah API.

This SDK is in beta. Pin an exact version until it reaches 1.0.0.

Install

npm install teiwah

The package supports both ESM imports and CommonJS require().

Quick start

Required configuration:

  1. Connected session — link the number in the Teiwah dashboard by scanning the session QR from WhatsApp (Settings → Linked devices → Link a device). Sending through a disconnected session returns 503.
  2. Session API key — copy it from the dashboard and provide it only to server-side code. The SDK validates it before making a request; the API returns 401 for a missing or invalid credential.
  3. Webhook URL — configure a public HTTPS endpoint reachable from Teiwah. Include the complete handler path, such as https://example.ngrok-free.app/webhook rather than the origin alone.

Initialize the SDK with the session API key. Reply using the inbound message's chatId unchanged:

import { Teiwah, type InboundMessage } from "teiwah"

const teiwah = new Teiwah({
  apiKey: process.env.TEIWAH_API_KEY!,
})

export async function reply(message: InboundMessage) {
  await teiwah.sendText({
    chatId: message.chatId,
    text: "Hello from Teiwah",
  })
}

Sending messages

The SDK provides one method for every supported message type:

await teiwah.sendImage({
  chatId,
  url: "https://example.com/image.jpg",
  caption: "Look at this",
})

// mimeType and filename are optional — Teiwah infers them from the URL/content.
await teiwah.sendPtt({
  chatId,
  base64: voiceNoteBase64,
})

await teiwah.sendDocument({
  chatId,
  url: "https://example.com/report.pdf",
})

Available methods:

  • sendText
  • sendImage
  • sendPtt
  • sendAudio
  • sendVideo
  • sendDocument
  • sendMessage for dynamic text or media requests
  • markMessageRead
  • showTyping
  • downloadMedia

Every media helper accepts exactly one of url or base64. TypeScript rejects requests containing both or neither. Prefer URLs for large files base64 input is limited to 16 MB decoded. mimeType and filename are inferred when omitted and remain available as optional overrides.

To quote a message, include its native WhatsApp ID:

await teiwah.sendText({
  chatId,
  text: "Got it, thanks!",
  quoteMessageId: inboundMessage.id,
})

Teiwah blocks cold one-to-one messages to contacts that have not previously engaged the connected account. Replies and group messages work normally; a blocked cold message returns HTTP 403.

Receiving messages

Teiwah sends inbound messages to the webhook configured for your session. The SDK exports the webhook payload type:

import type { InboundMessage } from "teiwah"

export async function handleWebhook(message: InboundMessage) {
  switch (true) {
    case "text" in message:
      // Text inbound — reply with sendText({ chatId: message.chatId, text: "..." }).
      break
    case "media" in message:
      // Media inbound — use media.url; PTT also includes media.base64.
      break
    default:
      // Handle unsupported message.
  }
}

Use chatId to reply to the conversation. In groups, chatId identifies the group while participant identifies the individual sender; participant is null in one-to-one conversations. contact is sender metadata, not a reply target.

PTT voice notes always include inline media.base64 for immediate processing. They also include media.url. Other inbound media is downloaded through media.url.

Inbound delivery is one POST per message, real-time and best-effort. Failed delivery and non-2xx responses are logged but not retried. Deduplicate using id if your handler runs more than once.

Chat actions

await teiwah.showTyping({ chatId })
await teiwah.markMessageRead({ messageId: inboundMessage.id })

Downloading inbound media

Use downloadMedia for images, audio, videos, and documents:

const { result, headers } = await teiwah.downloadMedia({
  id: inboundMessage.id,
})

// result is a ReadableStream<Uint8Array>

Errors

API failures are typed errors:

import { ErrorResponse } from "teiwah/models/errors"

try {
  await teiwah.sendText({ chatId, text: "Hello" })
} catch (error) {
  if (error instanceof ErrorResponse) {
    console.error(error.message, error.data$)
  } else {
    throw error
  }
}

Common API statuses:

  • 401 — the session API key is missing or invalid
  • 403 — cold one-to-one outreach is blocked
  • 404 — inbound media is unknown or no longer available for download
  • 413 — decoded base64 exceeds 16 MB
  • 422 — Teiwah could not fetch the supplied media URL
  • 503 — the WhatsApp session is offline

Retry behavior

The SDK does not automatically retry requests. A failed send can be ambiguous: WhatsApp may have accepted the message even if the connection failed before Teiwah returned a response. Retrying could therefore send duplicates.

Security

Teiwah API keys control a connected WhatsApp session. Never expose them in browser code, mobile applications, public repositories, logs, or client-side environment variables. This SDK rejects construction in a browser environment.

Runtime support

  • Node.js 20 or newer
  • Bun 1 or newer
  • Deno with npm package support

See RUNTIMES.md for details.

Development

The transport client and models are generated from openapi.yaml by Speakeasy. The public convenience façade is maintained as regeneration-safe custom code. See CUSTOMIZATIONS.md.

npm run build
npm run lint
npm test
npm run test:types

License

MIT