Send and receive WhatsApp from Node.js
Receiving is a route that answers Meta's verification GET and accepts POSTs. Sending is a fetch call to the Cloud API with the number's phone number id and token. There is no SDK of ours and nothing to install.
- One route, one fetch
- No SDK, no dependency on us
- Official Cloud API, not a linked device
Nothing charged for 7 days. Cancel anytime.
A GET for Meta's verification and a POST for events.
Window after a customer message in which free text sends.
The error returned when you send free text outside that window.
How do you receive messages in Node?
One route with two methods, in Express, Fastify, a Next route handler or a plain server.
The GET answers Meta's verification: read hub.mode, hub.verify_token and hub.challenge from the query, compare the token to yours, and return the challenge as plain text. Returning it as JSON is the usual mistake and produces an endpoint that never receives anything while looking correct.
The POST receives events. Respond 200 immediately and process afterwards. Meta retries anything slow, and a handler that does its work before responding will see the same message more than once. On serverless in particular, returning first and queueing the work is what keeps a cold start from turning into a duplicate.
How do you send?
A fetch to the Cloud API messages endpoint for the phone number id, with the token as a bearer header.
Nothing about it is specific to us, so the request matches Meta's own documentation exactly and there is no wrapper to learn or to be locked into. Both values are in the dashboard, and get_api_credentials returns them through the MCP connector if an assistant is doing the wiring.
The body depends on timing. Inside 24 hours of the customer's last inbound message, send a text object. Outside it, send a template object with an approved template name and language. Code that only sends text passes every test and fails on the first message that arrives overnight.
What is different on serverless?
Two things, and both are about the function ending before the work does.
Returning 200 and then continuing to process does not survive a function that freezes the moment it responds. Use whatever your platform offers to keep work alive after the response, or push the payload onto a queue and let a separate function handle it. Meta only needs the acknowledgement quickly; it does not need the work done.
Cold starts also make slow responses more likely, which means more retries and more duplicates. Deduplicating on the wamid message id is not optional in this shape, it is the thing that makes it reliable.
Common mistakes
- Returning the challenge as JSON rather than as the raw body.
- Doing the work before responding 200. On serverless the function may freeze the moment it responds.
- Skipping deduplication. Meta retries by design and duplicates are normal, not an edge case.
Connect the number at easycoexistence.com, set the webhook destination to your route, and read the phone number id and token from the dashboard. US$ 9 per number per month, first 7 days free.
Frequently asked questions
Do I need a library?
No. A route and fetch are enough, and the call matches Meta's documentation exactly.
Does this work on Vercel or Lambda?
Yes, with the usual serverless caveat: acknowledge first, then process through a queue or a background mechanism rather than inline.
How do I verify the request came from Meta?
Check the signature header against your app secret. Worth doing as soon as the endpoint is public.
Can I use TypeScript?
Yes. There is nothing to type against from us, since the payload is Meta's and documented by Meta.
Keep reading
Ready to get started?
Set up WhatsApp Coexistence in minutes, not months. The app keeps working on the phone.
Start Free TrialNothing charged for 7 days. Cancel anytime.Verified on