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

@askexenow/exe-gateway

v0.3.19

Published

Standalone webhook server + messaging gateway for multi-platform bot deployment

Downloads

256

Readme


exe-gateway is a self-hosted messaging gateway that connects WhatsApp, Telegram, Discord, Slack, Email, iMessage, Signal, Webchat, and Webhooks behind a single REST API. It handles rate limiting, typing simulation, and multi-account support out of the box — so your messages look human, not automated.

Features

| Feature | Details | |---------|---------| | 10 platform adapters | WhatsApp (Baileys), Telegram (Grammy), Discord, Slack, Email (SMTP/IMAP), iMessage, Signal, Webchat, Webhook, CRM | | Multi-account | Unlimited numbers per platform. Each account gets its own session and rate limiter. | | Human-like sending | Typing simulation, randomized delays between messages, per-recipient pacing. | | Anti-ban rate limiting | Per-platform hourly/daily caps tuned to each platform's tolerance. | | Full history sync | First WhatsApp link pulls complete message history. | | REST API | Send messages, list groups, check contacts, view rate limits, health checks. | | One-command install | Single curl command sets up Node.js, clones the repo, builds, configures systemd. | | Production-ready | Systemd service, nginx config, Docker support, security-hardened. | | Standalone | Works on its own. Optional hooks for Exe OS integration. |

Quick Start

One-command install (VPS — Ubuntu 22.04+ / Debian 12+)

curl -fsSL https://raw.githubusercontent.com/AskExe/exe-gateway/main/install.sh | bash

This installs Node.js 20, clones the repo to /opt/exe-gateway, builds from source, creates a system user, writes /home/exe/.exe-os/gateway.json, writes /etc/exe-gateway/exe-gateway.env, and installs the systemd service.

Pair WhatsApp

sudo -u exe node /opt/exe-gateway/pair-whatsapp.mjs my-account +1234567890

The pairing script reads the same config and proxy settings as the gateway, then stores the session under /home/exe/.exe-os/.auth/.

Start the service

systemctl start exe-gateway

Verify

curl -H "Authorization: Bearer <your-token>" http://127.0.0.1:3100/health

The raw auth token is shown once during installation; only its SHA-256 hash is stored in /etc/exe-gateway/exe-gateway.env.

Manual install (any platform)

git clone https://github.com/AskExe/exe-gateway.git
cd exe-gateway
npm ci
npm run build
mkdir -p ~/.exe-os
cp deploy/gateway.example.json ~/.exe-os/gateway.json
cp deploy/.env.example .env
set -a && source .env && set +a
node dist/bin/exe-gateway.js

WhatsApp IP Safety

Running on a local machine (laptop, home server, office network)?

You're already on a residential IP — skip this section entirely. Tailscale is not needed. Just install, pair, and go.

WhatsApp actively detects and bans datacenter IP addresses. If you're running exe-gateway on a VPS or cloud server (AWS, DigitalOcean, Hostinger, Hetzner, etc.), you must route WhatsApp traffic through a residential IP.

Why this matters

Connecting WhatsApp directly from a datacenter IP will trigger verification loops or outright bans. This is WhatsApp's anti-automation measure — it flags IPs that belong to known hosting providers.

Solution A: SOCKS proxy (recommended)

Route only WhatsApp traffic through your home machine via a lightweight SOCKS5 proxy over Tailscale. This avoids routing ALL VPS traffic through the exit node, which can break Cloudflare and other services.

VPS (Cloud/Datacenter)                Home Machine
┌─────────────────────────┐           ┌─────────────────────────┐
│  exe-gateway             │           │  microsocks (SOCKS5)     │
│  Baileys + SocksProxy    │◄─────────►│  bound to Tailscale IP   │
│  (only WhatsApp traffic) │ Tailscale │  (residential IP)        │
└─────────────────────────┘  mesh     └─────────────────────────┘
                                                ↓
                                        WhatsApp servers
                                        see: residential IP

1. Install a SOCKS5 proxy on your home machine:

# macOS
brew install microsocks
microsocks -i $(tailscale ip -4) -p 1080 &

# Linux
apt install microsocks
microsocks -i $(tailscale ip -4) -p 1080 &

2. Configure exe-gateway to use the proxy:

# In /etc/exe-gateway/exe-gateway.env
WHATSAPP_PROXY_URL=socks5://<home-tailscale-ip>:1080

3. Verify:

# From VPS — should show your home IP
curl --socks5-hostname <home-tailscale-ip>:1080 https://ifconfig.me

Solution B: Tailscale exit node (simpler but routes all traffic)

Route ALL VPS traffic through a home machine using Tailscale's exit node feature. Simpler setup but can interfere with Cloudflare, nginx, and other services on the VPS.

# Home machine
tailscale up --advertise-exit-node

# VPS
tailscale set --exit-node=<home-machine-name>

# Verify
curl -s ifconfig.me  # Should show HOME IP

Warning: Exit node routes all traffic, including return paths for inbound connections. If your VPS serves websites behind Cloudflare or a reverse proxy, use Solution A instead.

See docs/tailscale-exit-node.md for troubleshooting, DNS issues, firewall config, and keeping the exit node online.

Multi-Account Configuration

Runtime config lives at ~/.exe-os/gateway.json by default, or the path set in EXE_GATEWAY_CONFIG. Secrets and environment overrides live in /etc/exe-gateway/exe-gateway.env in the installer flow.

For PostgreSQL you can either set DATABASE_URL=postgresql://user:pass@host:5432/dbname or use the split EXE_GATEWAY_DB_* variables from deploy/.env.example.

{
  "port": 3100,
  "whatsappVerifyToken": "",
  "adapters": {
    "whatsapp": {
      "enabled": true,
      "credentials": {
        "app_secret": "set-if-you-use-meta-whatsapp-webhooks"
      },
      "accounts": [
        {
          "name": "sales",
          "authDir": "/home/exe/.exe-os/.auth/whatsapp-sales"
        },
        {
          "name": "support",
          "authDir": "/home/exe/.exe-os/.auth/whatsapp-support"
        }
      ]
    },
    "telegram": {
      "enabled": true,
      "accounts": [
        {
          "name": "main-bot",
          "bot_token": "123456:ABC-DEF...",
          "secret_token": "set-if-you-use-telegram-webhooks"
        }
      ]
    },
    "discord": {
      "enabled": true,
      "accounts": [
        {
          "name": "community-bot",
          "bot_token": "your-discord-token",
          "public_key": "set-if-you-use-discord-interactions"
        }
      ]
    },
    "slack": {
      "enabled": true,
      "accounts": [
        {
          "name": "workspace-bot",
          "bot_token": "xoxb-...",
          "app_token": "xapp-..."
        }
      ]
    },
    "email": {
      "enabled": true,
      "accounts": [
        {
          "name": "notifications",
          "smtp_host": "smtp.example.com",
          "smtp_port": "587",
          "smtp_user": "[email protected]",
          "smtp_pass": "your-password",
          "from_address": "[email protected]"
        }
      ]
    }
  }
}

Admin API bearer tokens are hashed at rest. Save the raw token once during install; the installer persists only EXE_GATEWAY_AUTH_TOKEN_HASH in /etc/exe-gateway/exe-gateway.env.

If you expose inbound provider webhooks, configure the matching signature secret in gateway.json:

  • WhatsApp (Meta Cloud API): adapters.whatsapp.credentials.app_secret → validates X-Hub-Signature-256
  • Telegram: accounts[].secret_token (or adapter-level secret_token) → validates X-Telegram-Bot-Api-Secret-Token
  • Discord interactions/webhooks: accounts[].public_key (or adapter-level public_key) → validates the Ed25519 signature headers

Pair each WhatsApp account separately:

sudo -u exe node /opt/exe-gateway/pair-whatsapp.mjs sales +1234567890
sudo -u exe node /opt/exe-gateway/pair-whatsapp.mjs support +0987654321

API Reference

/api/* and /v1/usage/* require the Authorization: Bearer <token> header (except /health). Provider webhook routes use provider-specific signature validation instead of the admin bearer token.

| Method | Endpoint | Description | |--------|----------|-------------| | GET | /health | Server health, uptime, registered platforms | | POST | /api/send | Send a message (rate-limited, with typing simulation) | | GET | /api/groups | List WhatsApp groups | | GET | /api/group/:id | Group metadata + participants | | GET | /api/limits | Rate limit stats per platform | | POST | /webhook/:platform | Incoming webhook payload from external platform |

Send a message

curl -X POST http://127.0.0.1:3100/api/send \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "platform": "whatsapp",
    "account": "sales",
    "to": "+1234567890",
    "text": "Hey — just following up on our conversation."
  }'

The message enters the outbound queue and is sent with realistic typing simulation and randomized delay. You don't need to manage pacing — the limiter handles it.

List groups

curl -H "Authorization: Bearer <token>" http://127.0.0.1:3100/api/groups

Check rate limits

curl -H "Authorization: Bearer <token>" http://127.0.0.1:3100/api/limits

Rate Limiting

Outbound messages are paced per-platform with human-like timing. These are the defaults — tuned to avoid platform bans:

| Platform | Per Hour | Per Day | Typing Simulation | Delay Between Messages | |----------|----------|---------|-------------------|----------------------| | WhatsApp | 30 | 200 | 1.5–8s (25 cps) | 5–15s per recipient | | Telegram | 60 | 500 | 1–5s (40 cps) | 2–8s per recipient | | Discord | 120 | 1,000 | 0.5–4s (50 cps) | 1–5s per recipient | | Slack | 120 | 1,000 | 0.5–4s (50 cps) | 1–5s per recipient | | Email | 20 | 100 | None | 10–30s per recipient |

Inbound messages are also rate-limited: 10 req/s per sender, 100 req/s global (sliding window).

Teddy Memory Scope

Teddy is read-only by default. You can further restrict which agent memories Teddy can read:

# Empty = all readable memory. Example below restricts memory reads to Mari/CMO only.
EXE_GATEWAY_TEDDY_MEMORY_AGENTS=mari

This is enforced in Teddy's tool handlers, not just the prompt. Requests for non-allowed agents return an access denied message.

Automated Outbound Guardrail

Automated outbound is deny-by-default. A fresh install can ingest and store WhatsApp messages, but it will not send bot responses, rate-limit notices, or auto-replies until an explicit response scope is configured.

There are three separate permission categories:

  1. DM sender — respond to this person only in direct messages.
  2. Whole group — respond to any allowed message in a specific group.
  3. Specific person in group — respond only when a specific number/JID speaks inside a group.
# empty = ingest-only, no automated replies
EXE_GATEWAY_RESPONSE_ALLOW_DMS=
EXE_GATEWAY_RESPONSE_ALLOW_GROUPS=
EXE_GATEWAY_RESPONSE_ALLOW_GROUP_PARTICIPANTS=

# DM only with one person
EXE_GATEWAY_RESPONSE_ALLOW_DMS=+16179354486,[email protected]

# Whole group by group JID
[email protected]

# Specific person in any group, or scoped to one group
EXE_GATEWAY_RESPONSE_ALLOW_GROUP_PARTICIPANTS=+16179354486
EXE_GATEWAY_RESPONSE_ALLOW_GROUP_PARTICIPANTS=120363428671509944@g.us:+16179354486

# Optional group command gate: even allowlisted groups/participants only get replies
# when the message starts with /ted or /ted<space>. DMs are unaffected.
EXE_GATEWAY_RESPONSE_GROUP_PREFIX=/ted
# Optional group stop command: allowed group speakers can send /stop to suppress
# an in-flight/stale group response. Default in code: /stop. Empty disables.
EXE_GATEWAY_RESPONSE_GROUP_STOP_COMMAND=/stop

# Legacy DM alias still supported
EXE_GATEWAY_RESPONSE_ALLOW_CONTACTS=+16179354486

# Emergency switch: true ignores all group response scopes
EXE_GATEWAY_RESPONSE_DM_ONLY=false

A DM allowlist never grants group response permission. This guard applies even if a bot is registered as the default route. It exists to prevent a newly linked WhatsApp account from unexpectedly messaging contacts or groups.

Teddy delegated sends

Teddy can be allowed to send WhatsApp text messages on Henry/operator's behalf from an approved DM. This is not auto-response: Teddy only sends when the approved operator explicitly asks it to send a specific message to a specific chat/contact.

# Empty = disabled. If unset, Teddy falls back to EXE_GATEWAY_RESPONSE_ALLOW_DMS.
EXE_GATEWAY_OPERATOR_SEND_ALLOW_DMS=+16179354486

Guardrails:

  • only approved direct-message senders get this write permission;
  • group prompts never grant delegated send permission;
  • Teddy can only use the send_whatsapp_message tool, not Exe OS task/memory writes;
  • recipient is resolved from stored WhatsApp groups/contacts, or an explicit JID/phone.

Operator notifications for contact segments

Operator notifications are separate from auto-response. They do not reply to the sender. They send a private alert to configured operator DMs when a watched contact messages the gateway.

This supports local/operator-defined contact segments using a suffix convention such as (Alpha), (VIP), or (Agency). The segment is not hardcoded; it is configured per deployment.

# OFF by default
EXE_GATEWAY_CONTACT_SEGMENT_NOTIFY_ENABLED=false

# When enabled, contacts whose alias/saved/local/display/push/profile name ends
# with this suffix trigger an operator notification.
EXE_GATEWAY_CONTACT_SEGMENT_NOTIFY_NAME_SUFFIX="(Alpha)"

# Send the alert to these operator DMs.
EXE_GATEWAY_CONTACT_SEGMENT_NOTIFY_RECIPIENTS=+16179354486

# Default false: only notify for DMs to the gateway, not group chatter.
EXE_GATEWAY_CONTACT_SEGMENT_NOTIFY_INCLUDE_GROUPS=false

Auto-Reply

Automatic replies for incoming messages — disabled by default, allowlist-gated, with 8 safety gates to prevent spam. Auto-reply allowContacts / allowGroups also opt those exact senders into the global automated outbound allowlist.

Add to gateway.json:

{
  "autoReply": {
    "enabled": true,
    "message": "Received. We'll get back to you shortly.",
    "allowGroups": ["[email protected]"],
    "allowContacts": ["+16179354486"],
    "cooldownHours": 24,
    "dailyCap": 20,
    "dmOnly": false
  }
}

| Setting | Default | Description | |---------|---------|-------------| | enabled | false | Master switch. No replies sent unless explicitly true. | | message | "Received." | Text to send as the auto-reply. | | allowGroups | [] | Only reply in these group JIDs. Empty = no group replies. | | allowContacts | [] | Only reply to these phone numbers/JIDs. Empty = no DM replies. | | cooldownHours | 24 | Minimum hours between replies to the same contact. | | dailyCap | 20 | Maximum total auto-replies per day across all contacts. | | dmOnly | false | If true, blocks all group replies regardless of allowGroups. |

Safety gates (always enforced, not configurable):

  1. Must be explicitly enabled (default OFF)
  2. Never replies to historical/sync messages
  3. Never replies to your own messages
  4. Never replies to empty or system messages
  5. Never replies to read receipts, reactions, or calls
  6. Only replies to allowlisted groups or contacts (must have at least one allowlist)
  7. Per-contact cooldown (default 24h)
  8. Daily cap (default 20)

Auto-replies include a random 3–15 second delay and typing indicator simulation to appear human.

Read-Only Mode

Background conversation monitoring — receives and stores all messages with zero bot footprint. No auto-reply, no typing indicators, no read receipts, no outbound sends.

Add to gateway.json:

{
  "readOnly": true,
  "database": { "host": "...", "port": 5432, "user": "...", "password": "...", "database": "..." }
}

What happens in read-only mode:

| Behavior | Status | |----------|--------| | Receive messages | ✅ Normal | | Store to PostgreSQL | ✅ Normal | | Pipeline ingest (CRM, contacts) | ✅ Normal | | History sync | ✅ Normal | | Auto-reply | 🔴 Force disabled | | Bot responses | 🔴 Suppressed | | Typing indicators | 🔴 Suppressed | | POST /api/send | 🔴 Rejected (403) | | Online presence | 🔴 Already off (markOnlineOnConnect: false) |

The /health endpoint shows "mode": "read-only" when active.

Combine with storageFilter to selectively ingest only specific groups/contacts.

Data Ingestion Adapters

exe-gateway serves as the data ingestion layer for the Exe platform. It runs API adapters (cron or webhook) that pull data from clients' external systems and stage it for routing into the wiki and CRM.

How it works

External APIs → exe-gateway adapters → staging.raw_imports → (routing handled downstream)

The gateway's job is to pull and stage — it does not route data into wiki or CRM schemas. Routing happens in a separate transform step after staging.

Adapter lifecycle

  1. Adapter runs on a configurable schedule (cron: 15min / hourly / daily)
  2. Checks staging.sync_cursors for the last pull position per source
  3. Pulls new/updated records from the external API
  4. Writes raw JSON to staging.raw_imports (same Postgres instance, staging schema)
  5. Updates sync_cursors with the new position

For platforms that support it (Stripe, Asana), webhooks provide real-time ingestion alongside cron-based pulls.

Supported adapters (current / planned)

| Adapter | Protocol | Status | |---------|----------|--------| | Xero | OAuth 2.0 REST | Planned | | Stripe | REST + Webhooks | Planned | | Asana | REST + Webhooks | Planned | | Banking APIs | Open Banking REST | Planned | | QuickBooks | OAuth 2.0 REST | Planned |

Configuration

Each adapter is configured via environment variables:

# Example: Xero adapter
XERO_CLIENT_ID=your-client-id
XERO_CLIENT_SECRET=your-client-secret
XERO_TENANT_ID=your-tenant-id
XERO_SYNC_INTERVAL=hourly    # 15min | hourly | daily

Related repos

  • exe-wiki/ARCHITECTURE.md — Full staging/routing architecture, schema definitions, routing rules
  • exe-crm — CRM-side entity mapping and how routed data lands in contacts/deals/activities

Integration with Exe OS (Optional)

exe-gateway is fully standalone. To integrate with Exe OS for memory, wiki, and CRM hooks:

import { setHooks } from "@askexenow/exe-gateway";
import { orgBus } from "@askexenow/exe-os/dist/lib/state-bus.js";
import { ingest } from "@askexenow/exe-os/dist/lib/pipeline-router.js";

setHooks({
  onEvent: (event) => orgBus.emit(event),
  onIngest: (msg) => ingest(msg),
});

This pipes all incoming messages through the Exe OS memory pipeline and broadcasts events to the organization bus.

Deployment

Systemd (recommended for VPS)

The installer sets this up automatically. To configure manually:

cp exe-gateway.service /etc/systemd/system/
mkdir -p /etc/exe-gateway
cp deploy/.env.example /etc/exe-gateway/exe-gateway.env
systemctl daemon-reload
systemctl enable --now exe-gateway

View logs:

journalctl -u exe-gateway -f

The service runs as a dedicated exe system user with security hardening. Tune NODE_OPTIONS and other secrets in /etc/exe-gateway/exe-gateway.env.

Nginx reverse proxy

For SSL termination and public-facing deployments:

cp nginx-gateway.conf /etc/nginx/sites-available/gateway.example.com
ln -s /etc/nginx/sites-available/gateway.example.com /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx

Replace the example domain and certificate paths first. The template now proxies /health, /webhook/*, /api/*, and /v1/*, and includes an optional /ws block for the WebSocket relay.

Docker

docker build -t exe-gateway .
cp deploy/gateway.example.json ./gateway-data/gateway.json
# Copy deploy/.env.example to your own env file and replace EXE_GATEWAY_AUTH_TOKEN first.
docker run -d \
  --name exe-gateway \
  -p 3100:3100 \
  -p 3101:3101 \
  --env-file ./gateway.env \
  -v ./gateway-data:/data \
  exe-gateway

WhatsApp v7 upgrade: QR re-link required

exe-gateway uses Baileys v7 for WhatsApp linked-device support. Baileys v7 is a breaking auth-state migration from 6.x. If you upgrade an existing WhatsApp-connected gateway, you must re-link each WhatsApp account once by scanning a new QR code.

Why: WhatsApp/Baileys changed the linked-device session format. Reusing old 6.x auth state can break receive/decrypt, especially for group messages.

Migration steps:

  1. Back up the current WhatsApp auth directory.
  2. Reset the account auth directory.
  3. Restart exe-gateway.
  4. Open https://<gateway-domain>/pair/default?token=<admin-token>.
  5. Scan from WhatsApp → Linked Devices.
  6. Verify direct receive, group receive, send, and history sync.

Installer users: if existing WhatsApp auth is detected, the installer stops instead of silently breaking the account. To intentionally back up and reset auth for v7, rerun with:

EXE_GATEWAY_CONFIRM_BAILEYS_V7_REPAIR=1 bash install.sh

No conversation data is deleted. Expect 2-5 minutes of WhatsApp downtime per account while re-linking. See docs/BAILEYS_V7_MIGRATION.md for the full runbook.

Platform Adapters

| Platform | Library | Status | Notes | |----------|---------|--------|-------| | WhatsApp | Baileys (Web protocol) | Production | Multi-account, full history sync, QR pairing | | Telegram | Grammy | Production | Bot API, inline keyboards, media | | Discord | discord.js | Production | Slash commands, threads, embeds | | Slack | Bolt + Web API | Production | Socket Mode, interactive messages | | Email | Nodemailer + IMAP | Production | SMTP outbound, IMAP inbound | | iMessage | macOS native | Beta | Requires macOS host | | Signal | signal-cli | Beta | Requires signal-cli daemon | | Webchat | WebSocket | Production | Browser widget, real-time | | Webhook | Generic HTTP | Production | Any platform via HTTP POST | | CRM | Exe CRM bridge | Production | Bi-directional contact/deal sync |

Contributing

  1. Fork the repo
  2. Create a feature branch (git checkout -b feat/my-feature)
  3. Make your changes with tests
  4. Run npm test and npm run typecheck
  5. Open a PR

License

MIT