Receives WhatsApp Business Cloud API webhooks, picks out group messages, translates them to English (from Hebrew by default), summarizes them, and stores every record in SQLite. Results are viewable at the /status endpoint.
The listener lives at:
- Live endpoint: [url:main.ts] (webhook root)
- Recent messages: [url:main.ts/status]
Rendering mermaid diagram...
- Meta sends a GET verification handshake containing
hub.challenge— we echo it back if thehub.verify_tokenmatches. - Meta sends POST notification events. We extract
textmessages that carry agroup_id, translate each, summarize it, and store it. GET /statusreturns the latest processed messages as JSON.
| Method | Path | Purpose |
|---|---|---|
| GET | / | Meta webhook verification handshake (hub.* query params) |
| POST | / | Receive webhook events (translate + summarize + store) |
| GET | /status | View recent processed messages as JSON |
| POST | /test | Simulate a group-message webhook to test the pipeline |
| POST | /digest | Email outstanding messages now (on-demand digest trigger) |
In addition to storing messages, a scheduled job emails you a digest of everything new since the last run. New messages are marked "digested" once included, so nothing is double-sent.
- Schedule:
0 5,12 * * *— 05:00 and 12:00 UTC (08:00 and 15:00 UTC+3). - Job file:
jobs/digest.ts(interval type); logic lives inlib/digest.ts. - Trigger on demand:
curl -X POST https://whatsapp-listener.val.run/digest - Recipient: defaults to the val owner's address; set
DIGEST_EMAILto override.
Required before Meta can talk to us:
WA_VERIFY_TOKEN— a secret string. Set the same value in Meta's App Dashboard so webhook verification passes.
Optional:
SOURCE_LANGUAGE— source language code. Defaults tohe(Hebrew) since that's the expected group language. Override (e.g.es,nl) for other groups, or leave unset to auto-detect each message withfranc.TARGET_LANGUAGE— target language, defaulten.MYMEMORY_EMAIL— your email; raises MyMemory's free translation quota from ~5K to ~50K chars/day.SUMMARY_MAX_SENTENCES— extractive summary length, default3.DIGEST_EMAIL— digest recipient. If unset, mail goes to the val owner's address.LLM_ENDPOINT/LLM_API_KEY/LLM_MODEL— optional; if all three are set, use an OpenAI-compatible chat-completions endpoint for higher-quality abstractive summaries (falls back to extractive otherwise).
Key env var URLs (set these in the Val Town UI):
👉 Add WA_VERIFY_TOKEN here: https://www.val.town/x/kineticalrisk/whatsapp-listener/environment-variables?key=WA_VERIFY_TOKEN 👉 Add SOURCE_LANGUAGE here: https://www.val.town/x/kineticalrisk/whatsapp-listener/environment-variables?key=SOURCE_LANGUAGE 👉 Add DIGEST_EMAIL here: https://www.val.town/x/kineticalrisk/whatsapp-listener/environment-variables?key=DIGEST_EMAIL 👉 Add MYMEMORY_EMAIL here: https://www.val.town/x/kineticalrisk/whatsapp-listener/environment-variables?key=MYMEMORY_EMAIL
curl -X POST https://whatsapp-listener.val.run/test
Then open https://whatsapp-listener.val.run/status to see the processed message.
- Create a WhatsApp Business account and number (Business Manager → WhatsApp).
- In the Meta App Dashboard, add the WhatsApp product and the
whatsapp_business_messagingpermission. - Under WhatsApp → Configuration, set:
- Callback URL:
https://whatsapp-listener.val.run/ - Verify token: the same value as
WA_VERIFY_TOKEN
- Callback URL:
- Click Verify and save — Meta calls your endpoint with the handshake.
- Subscribe to the
messageswebhook field. - Make sure your business number is a participant of the group whose messages you want to listen to. Test by sending a message in that group.
- Only group messages are processed (messages with a
group_id); 1:1 chats are ignored. - The official API only surfaces messages while your business number is in the group — there's no way to read arbitrary personal groups.
- Media messages (images/videos/audio) are stored as
[Image]/[Video]/[Audio]placeholders. - Translation uses MyMemory (free tier). It's fine for low-volume groups; for heavy volume add
MYMEMORY_EMAILor an LLM key.