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

@johann0301/whatsapp-rsvp

v0.1.0

Published

Embeddable Lu.ma RSVP integration for WhatsApp agents — installs as a library inside the host application.

Downloads

61

Readme

@johann0301/whatsapp-rsvp

Embeddable Lu.ma RSVP integration for WhatsApp agents.

This package is a library, not a service. It installs into a host application (Stripe SDK style), receives the host's already-open MongoDB connection, resolves credentials via a host callback, and keeps HTTP routes, workers, WhatsApp delivery, and retry queues in host territory.

  • Syncs Lu.ma events and guests into the host's MongoDB.
  • Confirms/declines RSVPs against the Lu.ma API with local caching.
  • Dispatches Lu.ma webhooks through an idempotent, signature-verified pipeline.
  • Multi-tenant by design: every operation is scoped by a tenantId.

Requirements

  • Node.js >= 18.17
  • MongoDB driver mongodb >= 6 (peer dependency — the host owns the connection)

Install

npm install @johann0301/whatsapp-rsvp mongodb

Quick start (single tenant)

import { MongoClient } from 'mongodb';
import { registerLumaService } from '@johann0301/whatsapp-rsvp';

const client = new MongoClient(process.env.MONGODB_URI);
await client.connect();

const { service, dispatchWebhook, mongoMode } = await registerLumaService({
  mongoClient: client,
  dbName: 'luma_rsvp',
  lumaToken: process.env.LUMA_API_TOKEN,
  webhookSecret: process.env.LUMA_WEBHOOK_SECRET,
  defaultCountry: 'BR',
});

// Sync an event and its guests from Lu.ma into MongoDB
await service.syncEvent('default', 'evt-123');

// List the events a phone number is invited to
const records = await service.getPersonEvents('default', '+5581999990001');

// Confirm attendance
const result = await service.rsvp('default', 'evt-123', '+5581999990001', 'approved');

With a single tenant, pass any stable identifier (for example 'default') as the tenantId in every call.

Multi-tenant

For hosts that serve multiple Lu.ma calendars, replace the static token with a tokenResolver callback. The library calls it with the tenantId of each operation; the host decides where credentials live (database, vault, env):

const { service, dispatchWebhook } = await registerLumaService({
  mongoClient: client,
  dbName: 'luma_rsvp',
  tokenResolver: async (tenantId) => {
    const creds = await hostDb.collection('tenants').findOne({ _id: tenantId });
    return { token: creds.lumaToken, webhookSecret: creds.webhookSecret };
  },
});

await service.syncEvent('acme', 'evt-123'); // resolved against tenant "acme"
await service.syncEvent('globex', 'evt-456'); // resolved against tenant "globex"

Every document the library writes carries the tenant_id, and every query filters by it — tenants never see each other's data.

Tenant id validation

tenantId values are validated at the public API boundary. Invalid values throw InvalidTenantIdError:

import { InvalidTenantIdError } from '@johann0301/whatsapp-rsvp';

try {
  await service.listEvents(''); // empty
} catch (err) {
  err instanceof InvalidTenantIdError; // true
}

A tenant id is rejected when it is empty, longer than 128 characters, or has leading/trailing whitespace.

Webhooks

registerLumaService returns dispatchWebhook. The host owns the HTTP route and passes the raw body plus signature headers through:

app.post('/webhooks/luma', express.raw({ type: 'application/json' }), async (req, res) => {
  const result = await dispatchWebhook({
    tenantId: 'default',
    rawBody: req.body, // Buffer — must be the unparsed payload
    signature: req.get('x-luma-signature'),
    timestamp: req.get('x-luma-timestamp'),
  });
  res.json(result);
});

dispatchWebhook verifies the HMAC signature and replay window, records the webhook id in an idempotency ledger (duplicates are acknowledged without reprocessing), and routes the event to the matching handler. The result is a discriminated union: processed, duplicate, malformed, or unknown_type.

Handled event types: guest.registered, guest.updated, event.created, event.updated, event.canceled, ticket.registered, calendar.event.added, and calendar.person.subscribed. Individual handlers can be overridden via the webhookHandlers option.

Security: configure webhookSecret (or webhookSecretFor / tokenResolver) before exposing the webhook route. dispatchWebhook rejects an empty resolved secret before signature verification.

Service API

| Method | Purpose | | ------------------------------------------------- | ------------------------------------------------------------------------------- | | service.syncEvent(tenantId, eventId) | Reconcile one event's guests from Lu.ma (upserts, soft-removes missing guests). | | service.listEvents(tenantId, filters?) | List cached events, optionally by status (active, cancelled, archived). | | service.getEvent(tenantId, eventId) | Fetch one cached event, or null. | | service.listGuests(tenantId, eventId, filters?) | List cached guests for an event. | | service.getPersonEvents(tenantId, phone) | Events a phone number participates in (E.164-normalized). | | service.rsvp(tenantId, eventId, phone, status) | Confirm (approved) or decline (declined) on Lu.ma + cache. |

Phone numbers are normalized to E.164 via defaultCountry; invalid numbers throw InvalidPhoneNumberError.

Errors

All errors extend LumaError: InvalidTenantIdError, InvalidPhoneNumberError, InvalidStatusTransitionError, WebhookSignatureError, WebhookReplayError, WebhookSecretError, RateLimitError, ConflictError, and NotFoundError.

MongoDB deployment modes

The library detects the deployment mode at registration time (mongoMode in the return value). A standalone MongoDB works out of the box; when a replica set is available, multi-document writes are automatically upgraded to transactions. No configuration required.

Indexes are created on registration; pass ensureIndexes: false to skip.

CLI acceptance harness

examples/cli ships a demo harness with a stateful mock Lu.ma API server — no real credentials needed:

npm run build
PORT=3002 node examples/cli/mock-luma-server.mjs   # terminal 1
node examples/cli/luma-cli.mjs                     # terminal 2 (see examples/cli/README.md)

Development

npm install
npm run typecheck
npm test
npm run build

See specs/ for the per-feature specifications driving the implementation.

License

MIT