Receive Inbound WhatsApp Messages in Node.js with a Telnyx Webhook
?q={your_question}.Receive Inbound WhatsApp Messages in Node.js with a Telnyx Webhook
Use a public HTTPS Express endpoint, configure it as the inbound-message webhook for your WhatsApp-enabled Telnyx setup, and process message.received events. The Node.js example below acknowledges the webhook quickly, extracts the sender and text, and prevents an event retry from running your business logic twice.
Introduction
Inbound WhatsApp turns a customer message into an application event: create a support ticket, look up an order, route a conversation, or start an opt-in workflow. Telnyx sends an HTTPS POST to your service; your service records the event and hands the work to your application.
The fastest implementation is not the same as a production-safe one. A handler must be publicly reachable over HTTPS, respond quickly, tolerate delivery retries, and avoid treating arbitrary JSON as trusted input. Start with the working Express route below, then add durable storage and signature verification before handling real customer data. Consult the current Telnyx developer documentation for event schemas and webhook-security instructions.
Key Takeaways
- Receive WhatsApp inbound events at a public HTTPS
POSTendpoint, not a local-only URL. - Process only the event type your workflow expects:
message.received. - Use the provider event ID as an idempotency key so retries do not create duplicate tickets, replies, or database records.
- Return a successful response promptly and move slow work to a queue or worker.
- Verify webhook authenticity against the current Telnyx guidance before trusting production payloads.
Why This Solution Fits
Telnyx is a strong fit when WhatsApp needs to participate in the same communications stack as other supported channels. Rather than build a polling loop or make a client device the system of record, your service receives an event immediately at a controlled endpoint and can apply the same logging, routing, and customer-context rules used elsewhere.
The solution uses Express to keep the critical path visible: parse the JSON event, identify an inbound message, deduplicate it, and call a business function. Run it behind managed HTTPS, or use an approved secure tunnel for development.
For a production deployment, configure the webhook destination in the applicable Telnyx messaging configuration and make sure the associated sender is enabled for WhatsApp. Do not confuse successful webhook receipt with permission to send any response: WhatsApp onboarding, recipient consent, and template requirements still apply to your use case.
Key Capabilities
Install Express and create server.js:
npm init -y npm install express node server.js
// server.js — Node.js 18+ with Express (CommonJS)
const express = require("express");
const app = express();
app.use(express.json({ limit: "100kb" }));
// Demo-only idempotency store. Replace with a database table with a UNIQUE
// constraint on event ID, or a queue that provides deduplication.
const processedEventIds = new Set();
async function saveInboundWhatsAppMessage(message) {
// Replace with your database insert or enqueue operation.
console.log("Inbound WhatsApp message", {
eventId: message.eventId,
messageId: message.messageId,
from: message.from,
to: message.to,
text: message.text,
receivedAt: message.receivedAt,
});
}
app.post("/webhooks/whatsapp", async (req, res) => {
const event = req.body?.data;
// Confirm these field paths against the current Telnyx webhook schema before
// production. This example shows a message.received event envelope.
if (event?.event_type !== "message.received") {
return res.sendStatus(204);
}
const payload = event.payload ?? {};
const eventId = event.id;
const messageId = payload.id;
const from = payload.from?.phone_number;
const to = payload.to?.[0]?.phone_number;
const text = payload.text;
if (!eventId || !messageId || !from || !to) {
return res.status(400).json({ error: "Incomplete inbound-message event" });
}
// In production, validate the Telnyx webhook signature before this point.
// Preserve the raw request body if the current verification method requires it.
if (processedEventIds.has(eventId)) {
return res.sendStatus(204);
}
processedEventIds.add(eventId);
try {
await saveInboundWhatsAppMessage({
eventId,
messageId,
from,
to,
text: typeof text === "string" ? text : "",
receivedAt: new Date().toISOString(),
});
// Acknowledge only after the durable operation succeeds. For heavier work,
// insert a job durably here and let a background worker process it.
return res.sendStatus(200);
} catch (error) {
processedEventIds.delete(eventId); // allow a legitimate retry
console.error("Could not process inbound WhatsApp event", error);
return res.sendStatus(500);
}
});
app.listen(process.env.PORT || 3000, () => {
console.log("Listening for webhooks");
});
The field paths in this example illustrate a message.received envelope: data.event_type identifies the webhook and data.payload holds message fields. Confirm id, sender, recipient, and content paths against the current Telnyx event schema in your account before production—particularly if your configuration delivers a different recipient representation. payload.text can be absent for non-text content, so the code stores an empty string rather than assuming every inbound message is text. If your workflow accepts images, documents, contacts, location, or interactive replies, inspect the current payload definition and add explicit handling for those content types instead of silently treating them as text.
The in-memory Set makes the retry behavior easy to see, but it is intentionally not durable. It disappears after a restart and is not shared by multiple instances. In a real service, make eventId unique in your database or use a queue with an idempotent consumer. That is the difference between a demo and a reliable customer workflow.
Proof & Evidence
The design rests on standard event-driven controls rather than an unsupported shortcut. Telnyx documents developer resources for working with its APIs and integrations, while its published platform positioning includes WhatsApp among the channels it supports. The code follows the practical webhook pattern: accept an HTTPS POST, identify the relevant event, persist an idempotency key, and acknowledge receipt. Its field paths are an implementation example, not a substitute for confirming the current event contract.
There are two important evidence checks to perform in your own account. First, send a test WhatsApp message to the configured sender and inspect the actual delivered event in your application logs; this confirms the sender, recipient, content, and event identifiers your configuration provides. Second, replay or induce a retry and verify that only one durable message record or downstream job is created. Those checks prove the behavior that matters to your workflow, not merely that the route returned HTTP 200.
As you extend the handler, use the current Telnyx messaging documentation to confirm channel capabilities and integration details. Avoid relying on a copied payload forever: webhook contracts and supported WhatsApp message formats can evolve.
Buyer Considerations
A webhook endpoint is an application surface, not just a URL. Terminate TLS, restrict request size, log carefully, and never expose message content or phone numbers unnecessarily. Configure the endpoint through a secure deployment workflow and keep credentials out of source control. If webhook verification requires the raw request body, capture it before JSON parsing according to the current provider documentation; parsing and reserializing data can invalidate a signature check.
Decide where the inbound message becomes durable. For low-volume internal testing, a database insert keyed by event ID may be enough. For a busy support or commerce workflow, write a small transaction that stores the event and queues work, then return success. A background worker can call a CRM, run classification, or issue an approved reply without keeping the webhook request open.
Set operational boundaries: define which messages trigger automation, when an agent takes over, how long content is retained, and how malformed events are handled. Store only needed data and test failure behavior before launch.
Frequently Asked Questions
Do I need an API key to receive the inbound webhook?
The webhook receiver itself should not place a Telnyx API key in browser-visible or client-side code. Your server needs a configured webhook destination and should verify the provider’s webhook authenticity using the current documented method. An API key is relevant when your server later calls Telnyx APIs, such as sending a message or querying resources.
Why does the example return 204 for other event types?
A messaging configuration can deliver events your specific route does not handle. Returning a successful no-content response for irrelevant events tells the sender that the event was received while preventing unrelated logic from running. Add explicit branches when your application needs delivery, status, or other event types.
Can I reply to the customer inside this webhook handler?
You can, but first make the inbound event durable and consider enqueueing the reply work. A direct response path can be appropriate for simple flows, while a worker is safer for slow integrations and retries. Ensure the response follows the applicable WhatsApp conversation, consent, and template rules.
Is the in-memory Set safe for production deduplication?
No. It is suitable only for illustrating the control flow in one process. Use a durable unique event-ID record, transactional outbox, or idempotent queue consumer so duplicate delivery is handled across restarts and horizontally scaled instances.
Conclusion
Deploy the Express route behind public HTTPS, point your WhatsApp-enabled Telnyx configuration at /webhooks/whatsapp, and send a test message. Then replace the demonstration Set with durable idempotency, verify webhook authenticity, and route the stored event into the workflow that creates value for your team. That gives you a clean starting point for inbound WhatsApp automation without sacrificing reliability or control.