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

@yaotoshi/openclaw-wa-sdk

v0.2.1

Published

TypeScript SDK for sending WhatsApp messages & reactions through the OpenClaw CAMIS gateway.

Readme

@yaotoshi/openclaw-wa-sdk

CI npm version

A tiny, fully-typed TypeScript SDK for sending WhatsApp messages and reactions through the OpenClaw CAMIS gateway.

  • Zero runtime dependencies — uses the global fetch (Node 18+, browsers, Bun, Deno).
  • Dual ESM / CommonJS — works with import and require.
  • Strict types + fail-fast validation — invalid payloads never reach the network.
  • Typed, actionable errors — discriminate on WaSdkError.code.

Designed to be easy for a coding LLM (or human) to use correctly: obvious method names, rich JSDoc, copy-paste examples, errors that say how to fix the problem.

Install

npm install @yaotoshi/openclaw-wa-sdk

Quick start

import { fromEnv, createWaClient } from "@yaotoshi/openclaw-wa-sdk";

// (a) from environment — reads OPENCLAW_WA_SDK_BASE_URL + OPENCLAW_WA_SDK_TOKEN
const wa = fromEnv();

// (b) explicit — testable, no env magic
const wa = createWaClient({
  baseUrl: "https://example.com",
  apiToken: process.env.OPENCLAW_WA_SDK_TOKEN!,
});

Works in CommonJS too:

const { fromEnv } = require("@yaotoshi/openclaw-wa-sdk");
const wa = fromEnv();

Environment variables (for fromEnv)

# .env
OPENCLAW_WA_SDK_BASE_URL=https://example.com   # gateway base URL (no trailing slash)
OPENCLAW_WA_SDK_TOKEN=xxxxx                          # must match the gateway's API_TOKEN_WA

Methods

| Method | Args | Returns | Notes | | --- | --- | --- | --- | | wa.sendMessage(args) | { to, message, mediaUrl? } | { messageId, toJid } | plain send | | wa.reply(args) | { to, messageId, message, participant?, self?, mediaUrl?, quotedText? } | { messageId, toJid } | group requires participant unless self:true | | wa.sendReaction(args) | { to, messageId, emoji, participant?, self? } | void | any emoji; "" removes | | wa.reactSuccess(args) | { to, messageId, participant?, self? } | void | sends ✅ | | wa.reactFailed(args) | { to, messageId, participant?, self? } | void | sends ❌ | | wa.reactRemove(args) | { to, messageId, participant?, self? } | void | removes the reaction |

to is an E.164 phone number for a personal chat (e.g. "+6281234567890") or a group JID (e.g. "120363…@g.us"). participant is the sender of the target message — a phone number (e.g. "+6281287657411") or JID. It's required for groups unless self: true.

Examples

Send a text message

const { messageId } = await wa.sendMessage({
  to: "+6281234567890",
  message: "Pesanan Anda dalam pengiriman 🚚",
});

Send media

await wa.sendMessage({
  to: "+6281234567890",
  message: "Surat jalan terlampir",
  mediaUrl: "https://example.com/surat-jalan.pdf",
});

Reply to a message

Reply to someone else's (inbound) message — pass the sender's participant for groups:

await wa.reply({
  to: "[email protected]",
  messageId: "<inbound-msg-id>",
  message: "Noted 👍",
  participant: "+6281287657411", // sender's phone (or JID); required for groups
});

Reply to your own message — set self: true (no participant needed):

await wa.reply({
  to: "[email protected]",
  messageId: "<my-msg-id>",
  message: "updated",
  self: true,
});

The self flag. self: true means "the target message is my own" → sets fromMe: true and auto-fills participant (with to) for groups. Omit it for someone else's message → fromMe: false, and participant is required for groups. Applies to reply and every reaction method.

React / remove a reaction

React with any emoji, or use the presets:

await wa.sendReaction({ to: "+6281234567890", messageId, emoji: "👍" }); // reacts with 👍
await wa.reactSuccess({ to: "+6281234567890", messageId });             // reacts with ✅
await wa.reactFailed({ to: "+6281234567890", messageId });              // reacts with ❌
await wa.reactRemove({ to: "+6281234567890", messageId });              // removes the reaction

React to a message in a group (someone else's message → pass participant):

await wa.reactSuccess({
  to: "[email protected]",
  messageId: "<inbound-msg-id>",
  participant: "+6281287657411", // required for groups
});

React to your own message in a group (no participant needed):

await wa.reactSuccess({
  to: "[email protected]",
  messageId: "<my-msg-id>",
  self: true,
});

Error handling

All failures throw a single {@link WaSdkError} class. Discriminate on code:

import { WaSdkError } from "@yaotoshi/openclaw-wa-sdk";

try {
  await wa.sendMessage({ to: "+62…", message: "hi" });
} catch (e) {
  if (e instanceof WaSdkError) {
    switch (e.code) {
      case "AUTH_ERROR":      console.error("bad token"); break;
      case "TIMEOUT":         console.error("gateway slow"); break;
      case "NETWORK_ERROR":   console.error("cannot reach gateway"); break;
      case "API_ERROR":       console.error("gateway:", e.message, e.status); break;
      case "INVALID_REQUEST": console.error("bad args:", e.message); break;
      case "MISSING_CONFIG":  console.error("config:", e.message); break;
    }
  }
}

| code | meaning | | --- | --- | | MISSING_CONFIG | baseUrl/apiToken missing or malformed, or env vars unset | | INVALID_REQUEST | client-side validation failed (missing field, group without participant) | | TIMEOUT | request exceeded timeoutMs | | AUTH_ERROR | gateway returned 401 (bad token) | | API_ERROR | gateway returned a non-2xx with { success:false, error } | | NETWORK_ERROR | request could not reach the gateway (DNS, connection) |

Configuration

createWaClient({ baseUrl, apiToken, timeoutMs: 30_000 }); // timeoutMs defaults to 30000

Requirements

  • A runtime with global fetch (Node ≥ 18, modern browsers, Bun, Deno).
  • fromEnv() is Node-only (reads process.env); the core client works anywhere.

License

MIT © onchainyaotoshi