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

@msgly/whatsapp

v0.2.2

Published

WhatsApp Cloud API adapter for Msgly

Readme

@msgly/whatsapp

WhatsApp Cloud API adapter for Msgly. Send and receive WhatsApp messages through the unified hub — text, all media types, interactive buttons, quick replies, reactions, and pre-approved templates. Zero classes, runs in Node, Next.js, and Edge runtimes.

Install

npm install @msgly/core @msgly/whatsapp

Quick start

import express from 'express';
import { createHub } from '@msgly/core';
import { createWhatsAppAdapter } from '@msgly/whatsapp';

const hub = createHub();

hub.register(
  createWhatsAppAdapter({
    phoneNumberId: process.env.WHATSAPP_PHONE_NUMBER_ID!,
    accessToken: process.env.WHATSAPP_ACCESS_TOKEN!,
    appSecret: process.env.META_APP_SECRET!,
    verifyToken: process.env.META_VERIFY_TOKEN!,
  }),
);

await hub.connect({ throwOnFailure: true });

hub.on('message', async (msg) => {
  if (msg.content.type === 'text') {
    await hub.send({
      channel: 'whatsapp',
      account: msg.account,
      contact: msg.contact,
      content: { type: 'text', text: `You said: ${msg.content.text}` },
    });
  }
});

const app = express();
app.use(express.json({ verify: (req, _r, buf) => ((req as any).rawBody = new Uint8Array(buf)) }));

const handlers = hub.createWebhookHandler();
app.get('/webhook/:channel', handlers.get);
app.post('/webhook/:channel', handlers.post);

app.listen(3000);

Config

interface WhatsAppConfig {
  phoneNumberId: string;  // long numeric id from API Setup
  accessToken: string;    // temporary (24h) or System User token
  appSecret: string;      // from App Settings → Basic
  verifyToken: string;    // your chosen string for webhook handshake
  apiBase?: string;       // defaults to https://graph.facebook.com
  apiVersion?: string;    // defaults to v20.0
}

Setup (20 minutes)

  1. Create a Meta App. Go to developers.facebook.com → My Apps → Create App → Business type.
  2. Add the WhatsApp product to your app → Set up.
  3. Copy test credentials from the API Setup tab:
    • Phone number ID (long numeric, NOT the human phone) → WHATSAPP_PHONE_NUMBER_ID
    • Temporary access token (24h) → WHATSAPP_ACCESS_TOKEN
  4. Get the App Secret. Settings → Basic → Show next to App Secret → META_APP_SECRET.
  5. Pick a verify token. Any random string → META_VERIFY_TOKEN.
  6. Add a test recipient. API Setup → "To" dropdown → Manage phone number list → add your personal WhatsApp number (max 5 in test mode).
  7. Subscribe the webhook. WhatsApp → Configuration tab:
    • Callback URL: <PUBLIC_URL>/webhook/whatsapp
    • Verify token: same as META_VERIFY_TOKEN
    • Click Verify and Save (your server must be running)
    • Webhook fields → Subscribe to messages
  8. Test. Message the test number from your personal WhatsApp.

Production tokens. The 24-hour token works for testing only. For production, create a System User token: Business Settings → Users → System Users → create one → Generate Token with scopes whatsapp_business_messaging and whatsapp_business_management. System User tokens don't expire.

Capabilities

| Feature | Supported | | ------------- | --------- | | text | ✓ | | image | ✓ | | video | ✓ | | audio | ✓ | | file | ✓ | | location | ✓ | | buttons | ✓ (max 3, 20-char labels) | | quick replies | ✓ | | templates | ✓ | | reactions | ✓ | | typing | — |

The adapter silently truncates button counts and label lengths to fit Meta's limits.

The 24-hour window

WhatsApp's policy: free-form replies (text, media, interactive) only work within 24 hours of an inbound user message. Outside that window you must send a pre-approved template.

Templates are created and approved in Meta dashboard → WhatsApp → Message Templates. Approval usually takes minutes for transactional templates.

await hub.send({
  channel: 'whatsapp',
  account: { channel: 'whatsapp', channelAccountId: process.env.WHATSAPP_PHONE_NUMBER_ID! },
  contact: { channel: 'whatsapp', channelUserId: '919999999999' },
  content: {
    type: 'template',
    templateName: 'order_confirmation',
    language: 'en',
    variables: { '1': 'Udesh', '2': 'ORDER-12345' },
  },
});

Variable keys are positional — '1' maps to {{1}} in the template body.

Sending examples

Image

await hub.send({
  channel: 'whatsapp',
  account, contact,
  content: {
    type: 'image',
    mediaRef: { kind: 'url', value: 'https://example.com/cat.png' },
    caption: 'meow',
  },
});

WhatsApp requires the URL to be publicly accessible HTTPS, or you can upload first:

const adapter = hub.getAdapter('whatsapp');
const ref = await adapter.uploadMedia({
  data: new Uint8Array(/* image bytes */),
  mimeType: 'image/png',
});

await hub.send({
  channel: 'whatsapp',
  account, contact,
  content: { type: 'image', mediaRef: ref, caption: 'meow' },
});

MediaFile.data accepts Uint8Array | Blob | ReadableStream<Uint8Array> — pass whichever your environment naturally produces.

Interactive buttons

await hub.send({
  channel: 'whatsapp',
  account, contact,
  content: {
    type: 'interactive',
    text: 'Confirm your order?',
    buttons: [
      { id: 'confirm', label: 'Confirm' },
      { id: 'cancel',  label: 'Cancel' },
    ],
  },
});

User taps a button → you receive a text message whose content.text equals the button's id.

Delivery receipts

WhatsApp delivers status updates (delivered/read/failed) as separate webhook events. The hub's standard webhook handler ignores these for hub.on('message') purposes — if you need granular delivery tracking, use adapter.parseStatuses(rawBody):

const adapter = hub.getAdapter('whatsapp') as WhatsAppAdapter;
const receipts = adapter.parseStatuses(req.body);
// [{ status: 'delivered', messageId: '...', timestamp: '...' }, ...]

Common pitfalls

  • (#131047) re-engagement message: you're outside the 24-hour window. Use a template.
  • (#131030) recipient phone not in allowed list: in test mode, recipients must be added under Manage phone number list. Business verification removes the limit.
  • InvalidSignature: wrong appSecret, OR your Express setup isn't capturing the raw body. The verify callback in express.json() is essential.
  • Verify handshake fails: META_VERIFY_TOKEN must match byte-for-byte between code and the form in the Meta dashboard. Server must be reachable when you click Verify.
  • Template send fails with (#132001): template name or language code doesn't match an approved template. Templates are case-sensitive.
  • Token expired after 24h: replace the temporary access token with a System User token.

Documentation

Full setup walkthrough and multi-channel usage: https://github.com/AyushJain070401/msgly

License

MIT