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

@whatsapp-wrapper-ultraguay/whatsapp-wrapper-ultraguay

v2.0.0

Published

Instanciable, type‑safe TypeScript wrapper for the WhatsApp Cloud API (Meta). Zero business logic — pure protocol wrapper.

Readme

WhatsApp Wrapper ULTRAGUAY v2

Instanciable, type‑safe TypeScript wrapper for the WhatsApp Cloud API (Meta).
Zero business logic — pure protocol wrapper.

npm version MIT License Node >= 18


🆕 What's New in v2

v2 is a complete rewrite from the ground up. If you're upgrading from v1, see MIGRATION.md for a step‑by‑step guide.

| Feature | v1 | v2 | |---------|----|----| | Architecture | Global singleton + standalone functions | Instanciable WhatsAppClient class | | Send methods | sendText, sendInteractive, sendTemplate | 13 send methods (text, image, video, audio, doc, sticker, location, template, reaction, contacts, interactive, location‑request) | | Return values | void | { wamid } on every send | | Inbound parsing | 5 message types | 15 message types | | Webhook | startWebhookServer (opinionated) | Event system (client.on(...)) + optional Express middleware | | HTTP | axios | Native fetch (zero deps) | | Retry | Basic | Exponential backoff with jitter, configurable | | Media | MediaClient (download only) | Full CRUD (upload, download, get URL, delete, download+save) | | Testing | enableMocking interceptors | MockWhatsAppClient + createMockWebhookPayload | | Config | .env via dotenv | Constructor config (multi‑client friendly) | | Dependencies | axios, dotenv, express, jimp, jsqr… | Zero runtime deps |


Highlights

  • Instanciable — create multiple clients for different phone numbers / tokens.
  • Complete send API — text, image, video, audio, document, sticker, location, location‑request, template, reaction, contacts, interactive (buttons, lists, flows, CTA).
  • Full inbound parsing — text, image, video, audio, document, sticker, location, contacts, interactive replies, reactions, flow replies, orders, system messages, referrals.
  • Media management — upload, download, get URL, delete.
  • Event‑drivenclient.on('message:text', …) with fully typed events.
  • Retry with backoff — automatic retry on 429 / 5xx with exponential backoff + jitter.
  • Native fetch — zero HTTP dependencies. Node ≥ 18 required.
  • Strict TypeScript — no any, full JSDoc, comprehensive exported types.
  • Subpath exports — import only what you need: ./webhook, ./storage, ./testing.

Install

npm install @whatsapp-wrapper-ultraguay/whatsapp-wrapper-ultraguay

Quick Start

import { WhatsAppClient } from '@whatsapp-wrapper-ultraguay/whatsapp-wrapper-ultraguay';

const client = new WhatsAppClient({
  accessToken: process.env.WA_TOKEN!,
  phoneNumberId: process.env.WA_PHONE_ID!,
  appSecret: process.env.WA_APP_SECRET, // optional, for webhook verification
});

// Send a text message
const { wamid } = await client.sendText('5215512345678', 'Hello from v2! 🚀');

// Send an image
await client.sendImage('5215512345678', { url: 'https://example.com/photo.jpg' }, {
  caption: 'Check this out!',
});

// Send a reaction
await client.sendReaction('5215512345678', wamid, '🔥');

// Mark as read
await client.markAsRead(wamid);

Configuration

const client = new WhatsAppClient({
  accessToken: string,        // Required: Meta access token
  phoneNumberId: string,      // Required: WhatsApp phone number ID
  appSecret?: string,         // For webhook signature verification
  apiVersion?: string,        // Default: 'v21.0'
  storage?: StorageAdapter,   // For media persistence (disk, S3, custom)
  http?: {
    timeoutMs?: number,       // Default: 30000
    maxRetries?: number,      // Default: 3 (only 429 & 5xx)
    backoffMs?: number,       // Default: 1000 (exponential with jitter)
  },
});

Send Methods

All send methods return Promise<{ wamid: string }>.

Text

await client.sendText(to, 'Hello!', {
  previewUrl: true,       // Enable link previews
  replyTo: 'wamid.xxx',  // Quote a message
});

Media (Image, Video, Audio, Document, Sticker)

Send by URL or by previously uploaded media ID:

// By URL
await client.sendImage(to, { url: 'https://example.com/photo.jpg' }, { caption: 'Nice!' });

// By media ID
await client.sendImage(to, { id: 'media-id-from-upload' });

// Video
await client.sendVideo(to, { url: 'https://example.com/video.mp4' }, { caption: 'Watch this' });

// Audio
await client.sendAudio(to, { url: 'https://example.com/audio.ogg' });

// Document
await client.sendDocument(to, { url: 'https://example.com/doc.pdf' }, {
  filename: 'invoice.pdf',
  caption: 'Your invoice',
});

// Sticker
await client.sendSticker(to, { url: 'https://example.com/sticker.webp' });

Location

await client.sendLocation(to, {
  latitude: 19.4326,
  longitude: -99.1332,
  name: 'Mexico City',
  address: 'CDMX, Mexico',
});

// Request user's location
await client.sendLocationRequest(to, 'Please share your location');

Template

await client.sendTemplate(to, 'hello_world', {
  language: 'es_MX',
  components: [
    {
      type: 'body',
      parameters: [{ type: 'text', text: 'World' }],
    },
  ],
});

Reaction

// Add reaction
await client.sendReaction(to, messageId, '👍');

// Remove reaction
await client.sendReaction(to, messageId, '');

Contacts

await client.sendContacts(to, [
  {
    name: { formatted_name: 'Jane Doe', first_name: 'Jane', last_name: 'Doe' },
    phones: [{ phone: '+15551234567', type: 'CELL' }],
  },
]);

Interactive (Buttons, Lists, Flows)

// Buttons
await client.sendInteractive(to, {
  type: 'button',
  body: { text: 'Choose an option:' },
  action: {
    buttons: [
      { type: 'reply', reply: { id: 'opt_a', title: 'Option A' } },
      { type: 'reply', reply: { id: 'opt_b', title: 'Option B' } },
    ],
  },
});

// List
await client.sendInteractive(to, {
  type: 'list',
  body: { text: 'Select from the menu:' },
  action: {
    button: 'View Menu',
    sections: [
      {
        title: 'Drinks',
        rows: [
          { id: 'coffee', title: 'Coffee', description: 'Hot coffee' },
          { id: 'tea', title: 'Tea', description: 'Green tea' },
        ],
      },
    ],
  },
});

Mark as Read

await client.markAsRead(messageId);

Media Management

// Upload
const { id } = await client.uploadMedia(fileBuffer, 'image/jpeg', 'photo.jpg');

// Get URL
const { url, mime_type, file_size } = await client.getMediaUrl(mediaId);

// Download
const buffer = await client.downloadMedia(mediaId);

// Delete
await client.deleteMedia(mediaId);

// Download and save (requires storage adapter)
const { location } = await client.downloadAndSave(mediaId, 'my-photo');

Webhook / Inbound Messages

Using the client's event system

client.on('message', (msg) => {
  console.log(`${msg.type} from ${msg.from}`);
});

client.on('message:text', (msg) => {
  console.log(`Text: ${msg.text}`);
});

client.on('message:image', (msg) => {
  console.log(`Image: ${msg.image.mediaId}`);
});

client.on('status', (status) => {
  console.log(`${status.id}: ${status.status}`);
});

client.on('error', (err) => {
  console.error('Webhook error:', err);
});

// In your webhook handler:
client.handleWebhook({
  rawBody: req.body,        // Raw body for signature verification
  signature: req.headers['x-hub-signature-256'],
  body: JSON.parse(req.body),
});

Standalone parsing

import { parseIncoming, parseStatuses } from '@whatsapp-wrapper-ultraguay/whatsapp-wrapper-ultraguay';

const messages = parseIncoming(webhookBody);
const statuses = parseStatuses(webhookBody);

Express middleware helper

import express from 'express';
import { createExpressMiddleware } from '@whatsapp-wrapper-ultraguay/whatsapp-wrapper-ultraguay/webhook';

const app = express();
app.use('/webhook', express.raw({ type: '*/*' }), createExpressMiddleware({
  appSecret: process.env.APP_SECRET!,
  verifyToken: process.env.VERIFY_TOKEN!,
  onMessage: (msg) => console.log('Message:', msg),
  onStatus: (status) => console.log('Status:', status),
  onError: (err) => console.error('Error:', err),
}));

Inbound message types

| Type | Description | |------|-------------| | text | Plain text message | | image | Image with optional caption | | video | Video with optional caption | | audio | Audio message (includes voice notes) | | document | Document with filename | | sticker | Sticker (static or animated) | | location | Shared location | | contacts | Shared contacts | | interactive_reply | Button or list reply | | reaction | Emoji reaction | | flow_reply | WhatsApp Flow response | | order | Product order | | system | System message (number change, etc.) | | referral | Click‑to‑WhatsApp ad referral | | unsupported | Unknown message type |

Storage Adapters

import { DiskStorageAdapter } from '@whatsapp-wrapper-ultraguay/whatsapp-wrapper-ultraguay/storage';

const client = new WhatsAppClient({
  accessToken: '...',
  phoneNumberId: '...',
  storage: new DiskStorageAdapter('/tmp/wa-media'),
});

// Download and persist in one call
const { location } = await client.downloadAndSave(mediaId);

S3 Storage

npm install @aws-sdk/client-s3  # peer dependency
import { S3StorageAdapter } from '@whatsapp-wrapper-ultraguay/whatsapp-wrapper-ultraguay/storage';

const client = new WhatsAppClient({
  accessToken: '...',
  phoneNumberId: '...',
  storage: new S3StorageAdapter({
    bucket: 'my-bucket',
    prefix: 'whatsapp-media/',
    s3: { region: 'us-east-1' },
  }),
});

Custom storage

import type { StorageAdapter } from '@whatsapp-wrapper-ultraguay/whatsapp-wrapper-ultraguay';

class MyStorageAdapter implements StorageAdapter {
  async save(input: { data: Buffer; mimeType: string; suggestedName?: string }) {
    // Your logic here
    return { location: 'custom://path' };
  }
}

Testing Utilities

import { MockWhatsAppClient, createMockWebhookPayload } from '@whatsapp-wrapper-ultraguay/whatsapp-wrapper-ultraguay/testing';

// Mock client records all calls
const mock = new MockWhatsAppClient();
await mock.sendText('123', 'hello');
console.log(mock.calls);       // [{ method: 'sendText', args: [...], timestamp: ... }]
console.log(mock.callsFor('sendText')); // filter by method

// Generate test webhook payloads
const payload = createMockWebhookPayload('text', { text: { body: 'Test!' } });
const imagePayload = createMockWebhookPayload('image');
const statusPayload = createMockWebhookPayload('status', { status: 'delivered' });

Package Exports

| Subpath | What it includes | |---------|-----------------| | . (main) | WhatsAppClient, parseIncoming, verifyWebhookSignature, all types, errors | | ./webhook | parseIncoming, parseStatuses, verifyWebhookSignature, createExpressMiddleware | | ./storage | DiskStorageAdapter, S3StorageAdapter, StorageAdapter interface | | ./testing | MockWhatsAppClient, createMockWebhookPayload |

TypeScript Types

Every type is exported for consumers — no need to re‑declare anything:

import type {
  // Config
  WhatsAppClientConfig,
  HttpOptions,
  // Send
  SendResponse,
  CommonSendOptions,
  SendTextOptions,
  MediaRef,
  SendImageOptions,
  SendVideoOptions,
  SendAudioOptions,
  SendDocumentOptions,
  SendStickerOptions,
  Location,
  SendTemplateOptions,
  TemplateComponent,
  TemplateParameter,
  // Contacts
  Contact,
  ContactName,
  ContactPhone,
  ContactEmail,
  ContactUrl,
  ContactAddress,
  ContactOrg,
  // Interactive
  Interactive,
  InteractiveButtons,
  InteractiveList,
  InteractiveSection,
  InteractiveRow,
  InteractiveFlow,
  InteractiveCTA,
  InteractiveLocationRequest,
  InteractiveHeader,
  SendInteractiveOptions,
  // Media
  UploadMediaResult,
  MediaUrlResult,
  // Inbound
  InboundMessage,
  InboundMessageType,
  InboundText,
  InboundImage,
  InboundVideo,
  InboundAudio,
  InboundDocument,
  InboundSticker,
  InboundLocation,
  InboundContacts,
  InboundInteractiveReply,
  InboundReaction,
  InboundFlowReply,
  InboundOrder,
  InboundSystem,
  InboundReferral,
  InboundUnsupported,
  // Status
  StatusUpdate,
  // Webhook
  WebhookPayload,
  // Events
  WhatsAppEvents,
  // Storage
  StorageAdapter,
} from '@whatsapp-wrapper-ultraguay/whatsapp-wrapper-ultraguay';

Error Classes

import { WhatsAppError, StorageNotConfiguredError } from '@whatsapp-wrapper-ultraguay/whatsapp-wrapper-ultraguay';

try {
  await client.sendText('123', 'Hello');
} catch (err) {
  if (err instanceof WhatsAppError) {
    console.log(err.statusCode);  // HTTP status code
    console.log(err.details);     // API error body
    console.log(err.retryAfter);  // seconds (if 429)
  }
}

Migration from v1

See MIGRATION.md for a comprehensive step‑by‑step guide with before/after code examples for every breaking change.

Requirements

  • Node.js ≥ 18 (for native fetch and FormData)
  • TypeScript ≥ 5.0 (recommended)

License

MIT