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

@molecule/api-channel-whatsapp

v1.0.2

Published

WhatsApp channel bond — implements @molecule/api-channel via the WhatsApp Cloud API for sendMessage (text/template/interactive), x-hub-signature-256 HMAC-SHA256 webhook verification, and inbound message parsing.

Readme

@molecule/api-channel-whatsapp

Auto-generated, AI-first package reference for the molecule.dev ecosystem. It is written to be read by coding agents as much as by people, and is generated from this package's source — edit src/index.ts JSDoc, not this file.

WhatsApp channel provider for molecule.dev.

Implements the framework-agnostic {@link ChannelProvider} interface over the WhatsApp Cloud API (https://graph.facebook.com/v22.0/<phone-id>/messages). Bond under the named-multi-provider 'channel' category at app startup:

Quick Start

import { setProvider } from '@molecule/api-channel'
import { provider } from '@molecule/api-channel-whatsapp'

setProvider('whatsapp', provider)

Type

provider

Installation

npm install @molecule/api-channel-whatsapp @molecule/api-channel @molecule/api-secrets

API

Interfaces

ProcessEnv

Environment variables consumed by the WhatsApp channel provider.

interface ProcessEnv {
  /** Cloud API access token. */
  CHANNEL_WHATSAPP_ACCESS_TOKEN: string

  /** Numeric phone-number id of the WhatsApp business sender. */
  CHANNEL_WHATSAPP_PHONE_NUMBER_ID: string

  /** Meta App secret used to verify `X-Hub-Signature-256`. */
  CHANNEL_WHATSAPP_APP_SECRET: string
}

WhatsAppButtonReply

Subset of the inbound button reply object (template button click).

interface WhatsAppButtonReply {
  /** Opaque payload originally set on the template button. */
  payload?: string

  /** Visible label of the button that was clicked. */
  text?: string
}

WhatsAppConfig

Configuration for the WhatsApp channel provider.

The access token is the credential that authorizes Cloud API calls — it is deliberately accepted only via this config (or the CHANNEL_WHATSAPP_ACCESS_TOKEN env var) and is NEVER included in error messages, log lines, or normalized payloads.

interface WhatsAppConfig {
  /**
   * WhatsApp Cloud API access token (typically a long-lived `EAA…`
   * system-user token). Defaults to the
   * `CHANNEL_WHATSAPP_ACCESS_TOKEN` env var.
   *
   * Treat as a secret — providers redact this value in any user-facing
   * output.
   */
  accessToken?: string

  /**
   * The numeric phone-number id assigned to the business in the Meta
   * developer dashboard (NOT the E.164 phone number). Used to build the
   * Cloud API endpoint. Defaults to the
   * `CHANNEL_WHATSAPP_PHONE_NUMBER_ID` env var.
   */
  phoneNumberId?: string

  /**
   * Meta App secret used to verify inbound `X-Hub-Signature-256`
   * webhook signatures. Defaults to the `CHANNEL_WHATSAPP_APP_SECRET`
   * env var.
   *
   * If unset, {@link WhatsAppChannelProvider.verifyWebhookSignature}
   * returns `false`.
   */
  appSecret?: string

  /**
   * Cloud API base URL. Override only for tests / staging endpoints.
   * Defaults to `https://graph.facebook.com`.
   */
  apiBaseUrl?: string

  /**
   * Cloud API version segment (e.g. `'v22.0'`). Defaults to `'v22.0'`.
   */
  apiVersion?: string

  /**
   * Per-request timeout in milliseconds. Defaults to 10000.
   */
  timeoutMs?: number
}

WhatsAppContact

Subset of the WhatsApp inbound contact object surfaced via {@link InboundMessage.payload}.

interface WhatsAppContact {
  /** Display name set by the user. */
  profile?: { name?: string }

  /** Sender's WhatsApp id (typically the E.164 phone number). */
  wa_id: string
}

WhatsAppInboundMessage

Subset of the inbound messages[] entry. Only fields the provider actually inspects are typed; others are passed through opaquely on {@link InboundMessage.payload}.

interface WhatsAppInboundMessage {
  /** WhatsApp message id (`wamid.…`). */
  id: string

  /** Sender's WhatsApp id (E.164 phone number). */
  from: string

  /** Unix timestamp (seconds, as a string) reported by WhatsApp. */
  timestamp?: string

  /**
   * Discriminator: `'text'`, `'image'`, `'audio'`, `'video'`,
   * `'document'`, `'sticker'`, `'location'`, `'button'`,
   * `'interactive'`, `'reaction'`, `'contacts'`, `'unsupported'`, …
   */
  type?: string

  /** Text body for `type === 'text'`. */
  text?: { body?: string }

  /** Inline media payloads. */
  image?: WhatsAppMediaRef
  audio?: WhatsAppMediaRef
  video?: WhatsAppMediaRef
  document?: WhatsAppMediaRef
  sticker?: WhatsAppMediaRef

  /** Geo payload for `type === 'location'`. */
  location?: WhatsAppLocation

  /** Template-button click for `type === 'button'`. */
  button?: WhatsAppButtonReply

  /** Interactive-button / list-reply for `type === 'interactive'`. */
  interactive?: WhatsAppInteractiveReply

  /** Reply context — present when this message replies to another. */
  context?: { id?: string; from?: string }
}

WhatsAppInteractiveReply

Subset of the inbound interactive reply object (button-list / list-reply click on an interactive message).

interface WhatsAppInteractiveReply {
  /**
   * Whether this was a `'button_reply'` or `'list_reply'`.
   */
  type?: string

  button_reply?: { id?: string; title?: string }

  list_reply?: { id?: string; title?: string; description?: string }
}

WhatsAppLocation

Subset of the WhatsApp inbound location object.

interface WhatsAppLocation {
  /** Geographic latitude in decimal degrees. */
  latitude: number

  /** Geographic longitude in decimal degrees. */
  longitude: number

  /** Optional human-readable place name. */
  name?: string

  /** Optional human-readable street address. */
  address?: string
}

WhatsAppMediaRef

Subset of the WhatsApp inbound media descriptor, shared by image / audio / video / document / sticker envelopes.

interface WhatsAppMediaRef {
  /** Cloud API media id (download via `/v22.0/<id>`). */
  id?: string

  /** Reported MIME type. */
  mime_type?: string

  /** Original filename (documents only). */
  filename?: string

  /** Optional caption (image / video / document). */
  caption?: string
}

WhatsAppOutboundExtensions

Optional WhatsApp-specific extensions carried via OutboundMessage.payload. Not part of the core {@link OutboundMessage} contract — providers SHOULD ignore unknown fields.

interface WhatsAppOutboundExtensions {
  /**
   * When set, the message is sent as a WhatsApp template instead of a
   * free-form text / interactive message.
   */
  template?: WhatsAppTemplateRef
}

WhatsAppSendResponse

Subset of the WhatsApp Cloud API outbound messages response that the provider inspects to build a {@link SendResult}.

interface WhatsAppSendResponse {
  /** Echoed phone-number id of the sending business. */
  messaging_product?: string

  /**
   * Per-recipient envelope. Cloud API always returns at least one
   * entry on success.
   */
  messages?: Array<{
    /** WhatsApp message id (e.g. `'wamid.HBgL…'`). */
    id: string
  }>

  /**
   * Cloud API error object on failure responses. Surfaced into the
   * thrown error message after token redaction.
   */
  error?: {
    message?: string
    type?: string
    code?: number
  }
}

WhatsAppTemplateRef

Identifies a WhatsApp template message to send. Templates are the only message form allowed outside the 24-hour customer-service window. The template (and its variables) must be pre-approved in the Meta WhatsApp Manager.

interface WhatsAppTemplateRef {
  /** Approved template name (e.g. `'order_confirmation'`). */
  name: string

  /** Template language code (e.g. `'en_US'`, `'es'`). */
  language: string

  /**
   * Optional positional body parameters substituted into the template
   * `{{1}}`, `{{2}}` placeholders. WhatsApp expects strings.
   */
  bodyParameters?: string[]
}

WhatsAppWebhookPayload

Top-level shape of an inbound WhatsApp Cloud API webhook payload.

interface WhatsAppWebhookPayload {
  /** Always `'whatsapp_business_account'` for Cloud API events. */
  object?: string

  /** Top-level entries (one per WABA). */
  entry?: Array<{
    /** WhatsApp Business Account id. */
    id?: string
    /** Per-product changes (`'messages'`, `'message_template_status_update'`, …). */
    changes?: Array<{
      field?: string
      value?: WhatsAppWebhookValue
    }>
  }>
}

WhatsAppWebhookValue

Subset of the entry[].changes[].value payload in a Cloud API webhook event.

interface WhatsAppWebhookValue {
  /** Always `'whatsapp'` for Cloud API events. */
  messaging_product?: string

  /** Sender / receiver metadata (display phone, phone-number id). */
  metadata?: { phone_number_id?: string; display_phone_number?: string }

  /** Inbound contacts that triggered this event. */
  contacts?: WhatsAppContact[]

  /** Inbound messages that triggered this event. */
  messages?: WhatsAppInboundMessage[]
}

Classes

WhatsAppChannelProvider

WhatsApp channel provider — implements the framework-agnostic {@link ChannelProvider} contract on top of the WhatsApp Cloud API.

Functions

createProvider(config)

Convenience factory for the named-multi-provider bond pattern.

function createProvider(config?: WhatsAppConfig): WhatsAppChannelProvider
  • config — Optional WhatsApp config.

Returns: A new {@link WhatsAppChannelProvider} instance.

Constants

channelWhatsappSecretDefinitions

Secret definitions required by the WhatsApp channel bond.

const channelWhatsappSecretDefinitions: SecretDefinition[]

provider

Lazily-instantiated singleton instance for app-startup wiring. Reads configuration from environment variables on first use.

const provider: ChannelProvider

Core Interface

Implements @molecule/api-channel interface.

Injection Notes

Requirements

Peer dependencies:

  • @molecule/api-channel ^1.0.1
  • @molecule/api-secrets ^1.0.1

Environment Variables

  • CHANNEL_WHATSAPP_ACCESS_TOKEN (required) — WhatsApp access token
  • CHANNEL_WHATSAPP_PHONE_NUMBER_ID (required) — WhatsApp phone number ID
  • CHANNEL_WHATSAPP_APP_SECRET (required) — Meta app secret (WhatsApp)

Runtime Dependencies

  • @molecule/api-channel
  • @molecule/api-secrets

Outbound messages outside the WhatsApp 24-hour customer-service window MUST be sent as approved templates. The provider exposes OutboundMessage.kind = 'rich' two ways: via interactive button objects (in-window) or via WhatsApp templates (out-of-window) when payload.template is supplied.

Webhook subscription needs a GET echo the bond does not provide. When registering the webhook URL in the Meta console, Meta sends GET ?hub.mode=subscribe&hub.verify_token=<your token>&hub.challenge=<n>; your route must validate the verify token and respond 200 with the raw hub.challenge. Only POST deliveries flow through verifyWebhookSignature() / parseInbound().

E2E Tests

Integration checklist — drive the real UI (live preview, no mocks), adapt each item to this app's actual screens/flows, and check every box off one by one. A box you can't check is an integration bug to fix — not a skip:

  • [ ] Each channel-notifying flow the app defines (a Slack/Discord alert on a new order, a status-change message) actually produces a message. The sandbox CAPTURES channel messages instead of sending — read them with the read_activity tool (filter type 'channel'); never mock the flow or modify production code to expose the message.
  • [ ] The captured message targets the configured channel/provider name and carries the app's real content (readable text, no undefined placeholders, no secrets).
  • [ ] A failed send (unbonded or misconfigured provider) is visible in logs/UI — never silently swallowed.