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

@pixelinfinito/twenty-app-whatsapp

v0.1.2

Published

WhatsApp for Twenty CRM — shared inbox, templates, consent-aware campaigns and delivery health on the Meta WhatsApp Cloud API.

Readme

WhatsApp for Twenty CRM

Two-way WhatsApp messaging inside Twenty: a shared team inbox, conversations on every Person record, approved templates, consent tracking and marketing campaigns — powered by the official Meta WhatsApp Cloud API.

Developed by Marcos Lisboa at Pixel Infinito · published as @pixelinfinito/twenty-app-whatsapp.

WhatsApp for Twenty

What you get

A shared team inbox. Filter by yours, unassigned, everyone's, unread or closed. Assignment, blocking and closing are one click, keyboard navigation included, and every action lands on the contact's timeline.

Shared inbox

The 24-hour rule, enforced server-side. The composer always knows whether you may type: window state, consent, blocking, number quality and template availability are decided by the server and explained in the interface — never silently hidden.

Templates you can actually use. Sync from Meta, see which are approved, publish the ones the CRM may render, and fill their parameters from CRM fields with a live preview before sending.

Template management

Campaigns with brakes. Build an audience from a saved view, bind template parameters per recipient, get a cost estimate before launch — while a share of the daily tier is held back for 1:1 traffic and a circuit breaker pauses any campaign whose failure rate climbs.

Campaigns

Consent that holds up. Opt-out and opt-in keywords, a single confirmation reply whose wording is a setting rather than a deploy, and an audit event recorded for every change.

Operations you can see. A health panel that names which of six things is wrong and what to do about it, the exact callback URL and webhook fields to paste into Meta, and diagnostics for failed deliveries and stuck sends.

Everything WhatsApp sends, handled. Text, images, audio, video, documents, stickers, locations, contact cards, reactions and replies all render; anything Meta invents next is stored and shown as an unsupported message rather than dropped.

The interface ships in English and Portuguese, following each user's locale.

Requirements

  • A Twenty workspace (cloud or self-hosted) whose public URL is reachable over HTTPS — Meta must be able to call your webhook.
  • A Meta Business account with an approved WhatsApp Business phone number.
  • A Meta app with the WhatsApp product and a system-user access token.

Installation

Install from Settings → Apps in your Twenty workspace, then work through the steps below in order. The order matters: you cannot test the proxy alias before the route exists, and the route does not exist before the app is claimed.

The three silent failures, first

Three setup steps produce a webhook that is configured perfectly in Meta, verifies successfully, and still delivers nothing. Each has its own status code, none is guessable, and Meta reports only an undetailed delivery failure. They are steps 1–3 below.

| If POST <base>/webhooks/server/bb76f114-… answers | Cause | Fix | | --- | --- | --- | | 404 LOGIC_FUNCTION_NOT_FOUND | The app registration is unclaimed, so its server route has no workspace to run in | Step 1 | | 403 LOGIC_FUNCTION_DISABLED | The server refuses to run any logic function | Step 2 | | 500 MissingConfigError | META_APP_SECRET is unset; the resolver fails closed rather than verify against an empty key | Step 3 | | 401 {"error":"invalid signature"} | Correct. The route is live and refusing an unsigned request | — |

The health panel's Webhook route (POST) row runs exactly this probe and names whichever of these it finds, so you can check it at any point rather than guessing.

1. Claim the app registration

Open Settings → Applications → WhatsApp → Danger zone and click Claim ownership.

The webhook's POST half is a server route, and a server route runs in the workspace that owns the application registration. While the registration is unclaimed there is no such workspace, so the platform has nowhere to dispatch it and the route does not exist. Everything else looks fine when this is wrong — the app is installed, all its logic functions are registered, the settings page renders.

If you don't see the button, the claim control only renders on the admin panel route: <base>/settings/admin-panel/applications/registrations/<registrationId>. It requires a signed-in user with full admin-panel access — an API key cannot do it.

2. Enable logic functions on the server

Set this in the Twenty server's environment (not an app variable) and restart:

LOGIC_FUNCTION_TYPE=LOCAL

LAMBDA is the alternative if you run functions on AWS. With neither, Twenty loads a driver that refuses every logic function — no routes, no scheduled jobs, nothing in this app works.

3. Create and configure a Meta app

  • In Meta for Developers, create an app of type Business and add the WhatsApp product.
  • From App Dashboard → Settings → Basic, copy the App ID and App Secret.

4. Create credentials

  • In Business Manager → System Users, create a system user with access to your WhatsApp Business Account.
  • Generate a long-lived token with exactly these scopes:
    • whatsapp_business_management
    • whatsapp_business_messaging
  • Generate a random verify token (for example openssl rand -hex 32).

5. Add the secrets in Twenty

Open Settings → Apps → WhatsApp → Settings → Variables and set all four:

| Variable | Value | | --- | --- | | META_APP_ID | App ID from step 3 | | META_APP_SECRET | App Secret from step 3 | | META_ACCESS_TOKEN | System-user token from step 4 | | META_VERIFY_TOKEN | Your random verify token |

All four are required. The resolver fails closed on a missing META_APP_SECRET rather than verifying every signature against an empty key — which is correct, and shows up only as a 500.

6. Configure the webhook in Meta

Meta takes one callback URL and uses it for two things: a GET carrying hub.challenge to verify the endpoint, and POST to deliver events. Twenty answers those on two different paths:

| | Path | Answered by | | --- | --- | --- | | GET verification | <base>/s/whatsapp/verify | wa-webhook-verify | | POST events | <base>/webhooks/server/bb76f114-7843-4a09-af64-9ceca78479cd | wa-webhook-resolver |

So add a one-line alias to the reverse proxy in front of Twenty that method-splits a single public path. Caddy:

example.com {
    @wa_verify { path /whatsapp/webhook
                 method GET }
    @wa_events { path /whatsapp/webhook
                 method POST }

    handle @wa_verify {
        rewrite * /s/whatsapp/verify?{query}
        reverse_proxy twenty:3000
    }
    handle @wa_events {
        rewrite * /webhooks/server/bb76f114-7843-4a09-af64-9ceca78479cd
        reverse_proxy twenty:3000
    }
    handle { reverse_proxy twenty:3000 }
}

Nginx:

location = /whatsapp/webhook {
    if ($request_method = GET)  { rewrite ^ /s/whatsapp/verify?$args last; }
    if ($request_method = POST) { rewrite ^ /webhooks/server/bb76f114-7843-4a09-af64-9ceca78479cd last; }
    return 405;
}

Cloudflare (no origin config needed — two URL Rewrite Transform Rules, under Rules → Overview → Create rule → URL Rewrite Rule). Your DNS record must be proxied:

Rule 1 — verification
  (http.host eq "crm.example.com" and http.request.uri.path eq "/whatsapp/webhook"
   and http.request.method eq "GET")
  Path → Static → /s/whatsapp/verify        Query → leave unchanged

Rule 2 — events
  (http.host eq "crm.example.com" and http.request.uri.path eq "/whatsapp/webhook"
   and http.request.method eq "POST")
  Path → Static → /webhooks/server/bb76f114-7843-4a09-af64-9ceca78479cd

Rewrite only the path in both, so hub.mode / hub.challenge / hub.verify_token survive on the GET. The two are mutually exclusive by method, so order does not matter. In the expression builder http.request.method appears as Request Method.

Turn off bot protection for this host. Cloudflare's AI Labyrinth and Bot Fight Mode classify Meta's webhook deliveries as crawler traffic and serve them decoy content, so they never reach Twenty. This is the worst failure mode in the whole setup, because it leaves no trace on your server at all: verification passes, the route answers every manual test correctly, and real deliveries vanish with nothing in any log. The only evidence is in Cloudflare → Security → Events, where the deliveries appear as AI Labyrinth Served.

Turn both off under Security → Settings → Bot traffic, or add a WAF custom rule on /whatsapp/webhook with the Skip action and every bot option selected. On a CRM host there is no content for an AI crawler to scrape, so the protection costs more than it earns.

Do not let the proxy buffer, re-encode or otherwise rewrite the request body: the HMAC is computed over the exact bytes Meta sent, and any normalisation invalidates every signature.

Then open Webhooks in the Meta app's WhatsApp product, set the callback to <your base URL>/whatsapp/webhook, paste the same verify token, and click Verify and save.

Can you skip the proxy? Only if your Twenty version routes GET to server routes. It does not on 2.31.x — verified on 2.31.1 and 2.31.6, where the GET returns the front-end shell instead of the challenge. Check yours with curl "<base>/webhooks/server/bb76f114-7843-4a09-af64-9ceca78479cd?hub.mode=subscribe&hub.challenge=probe&hub.verify_token=<your token>". If it returns probe, point Meta straight there. If it returns HTML, you need the alias.

7. Subscribe the webhook fields

Subscribe all seven: messages, message_template_status_update, message_template_quality_update, message_template_components_update, account_update, phone_number_quality_update, business_capability_update.

Without message_template_status_update, template approvals never arrive.

8. Connect your number, then test

In the app settings, connect your WABA and phone number, run the health check, and sync templates.

Connect the number before testing the webhook. Meta's "Send to my server" sample carries a phone_number_id nobody has claimed. The resolver answers it 200 and discards it — deliberately, since a non-2xx would earn a week of Meta retries for a number you do not host — so it leaves no row at all in Diagnostics. Run against an unconnected number, a perfectly working webhook and a broken one look identical.

Once your number is connected, a test delivery appears under Diagnostics, and the health panel verifies each remaining piece.

Troubleshooting

Seeing unverified webhook, signature failed, or no events?

  • Nothing in the logs at all? If a real delivery leaves no trace — no signature_rejected, no unclaimed, no row — then it never reached your server, and the cause is in front of it. On Cloudflare, check Security → Events for AI Labyrinth Served or a bot challenge on /whatsapp/webhook (see step 6). Silence in your own logs is evidence about the edge, not about the app.
  • Start with the Webhook route (POST) row in the health panel. It probes the route directly and names the cause — see the table at the top of Installation for what each status means. This is the single most likely explanation of "verification passed but no events ever arrive".
  • Is your number connected? An unclaimed number's delivery is accepted and discarded with no row (step 8). Test with a connected number or you cannot tell success from failure.
  • Confirm the proxy alias from step 6 exists in both directions. A callback that verifies but delivers nothing is the classic symptom of a GET rule without its POST counterpart — the POST then 404s somewhere you will only see in the proxy log.
  • Re-copy the callback URL and verify token into Meta from the latest save.
  • Confirm the endpoint is reachable over HTTPS and not behind an IP/VPC block.
  • Confirm all webhook fields above are subscribed.
  • Confirm the system-user token still has both WhatsApp scopes.

The health panel diagnoses each of these individually.

Configuration

Beyond the Meta secrets, the app exposes ~30 application variables so operational behavior never requires a deploy — send throttles and pacing, campaign batch sizes and failure thresholds, opt-in/opt-out keywords and confirmation wording (English and Portuguese), retention windows, timeline verbosity, per-category pricing for cost estimates, and more. Each variable is documented in place under Settings → Apps → WhatsApp.

Per-number settings (throttle, default calling code, auto-assignment, contact auto-creation) live on the WhatsApp account record, so numbers can differ.

Development

yarn install
yarn twenty docker:start   # local Twenty server
yarn twenty dev            # sync the app and watch

See SETUP.md for the full local guide, specs/ for design and architecture notes, and CHANGELOG.md for notable changes. yarn test:unit runs the local test suite; yarn test runs integration tests against a disposable workspace.

Learn more

License

MIT © Pixel Infinito