Integrations

Chatwoot with the WhatsApp API: support through the API channel inbox

To handle WhatsApp in Chatwoot using the D-API WhatsApp API, create an API channel inbox and put a middleware between the two. Customer messages enter Chatwoot through its API, and agent replies leave through the Chatwoot webhook to D-API.

By D-API engineering team5 min read

Why the path is the API channel inbox

Chatwoot has ready-made channels for some providers, and D-API is not one of them. For those cases it offers the API channel inbox: a generic channel where an external system creates contacts, conversations and messages through the Chatwoot API, and Chatwoot notifies that system through a callback URL when an agent replies.

Neither side speaks the other’s language, so something has to translate. That something is the middleware, a small service you host. There is no native connector between D-API and Chatwoot, and the integration below is done entirely over HTTP and webhooks.

The path of each message

Customer writes on WhatsApp

  1. The customer messages the number connected on D-API.
  2. D-API fires messages.received to the middleware’s inbound URL.
  3. The middleware looks up the contact by phone number. If it does not exist, it creates the contact in the API inbox and stores the returned source_id.
  4. If there is no open conversation for that contact, it creates one and stores the ID.
  5. It creates the message in the conversation with message_type: "incoming". The agent sees the message in the dashboard.

Agent replies in Chatwoot

  1. The agent writes in the conversation.
  2. Chatwoot calls the inbox callback URL with the message_created event.
  3. The middleware drops anything that is not a reply to the customer: incoming messages (the ones it created itself) and notes with private set to true.
  4. Using the conversation ID, it finds the phone number and sends the text to D-API’s /api/v1/messages/send/text.

Setting up both sides

In Chatwoot, go to Settings, Inboxes, Add Inbox and choose API. Enter a channel name and, in the webhook field, the middleware’s outbound URL. Then add the agents who will handle the inbox. To call the Chatwoot API, the middleware uses an access token generated in a user’s profile, sent in the api_access_token header.

On D-API, point the connection’s incoming message event to the middleware’s inbound URL:

curl -X POST https://api.d-api.cloud/api/v1/sessions/support/webhook-config \
  -H "Authorization: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "enabled": true, "type": "single",
        "events": { "messages.received": { "enabled": true, "webhookUrl": "https://middleware.yourcompany.com/inbound" } } }'

The calls the middleware makes to Chatwoot, all with the token header:

WhenChatwoot call
Customer’s first contactPOST /api/v1/accounts/ID/contacts with inbox_id, name and phone number
No open conversationPOST /api/v1/accounts/ID/conversations with source_id, inbox_id and contact_id
Each incoming messagePOST /api/v1/accounts/ID/conversations/ID/messages with content and message_type incoming

Outbound: from the Chatwoot callback to D-API

This is the part that raises the most questions, so it is worth seeing in code. The route below receives the inbox callback and replies to the customer. findConversation stands for your mapping table:

app.post('/outbound', async (req, res) => {
  res.sendStatus(200)
  const ev = req.body
  if (ev.event !== 'message_created') return
  if (ev.message_type !== 'outgoing' || ev.private) return

  const link = await findConversation(ev.conversation.id)
  if (!link) return // conversation that did not start on WhatsApp

  await fetch('https://api.d-api.cloud/api/v1/messages/send/text', {
    method: 'POST',
    headers: { Authorization: process.env.DAPI_KEY, 'Content-Type': 'application/json' },
    body: JSON.stringify({ sessionId: link.sessionId, to: link.phone, text: ev.content }),
    signal: AbortSignal.timeout(10000),
  })
})

Note the message_type filter. Without it, every message the middleware creates as incoming fires the callback back and can turn into a duplicate send to the customer.

What the middleware needs to do well

  • Persistent mapping: phone number to contact and conversation, conversation to phone number and sessionId. In a database, not in memory, to survive deploys and run on more than one instance.
  • Idempotency: D-API resends the webhook when the URL fails or takes longer than 30 seconds. Use the message’s data.id as the key and ignore what has already been logged.
  • Fast response: return 200 right away and process afterwards, or in a queue. Calling Chatwoot three times inside the webhook request is asking for a timeout.
  • Messages from the phone itself: the received event also arrives with fromMe set to true when someone writes from the device. Decide whether that goes into Chatwoot as a note or is ignored.
  • Dropped connection: also subscribe to connection.status to alert the team when the number disconnects, instead of finding out from a stalled queue.

When this design pays off

If your company wants a support desk without building screens, Chatwoot plus the WhatsApp API gets it done with a small service in the middle. With several numbers, one inbox per connection keeps each team on its own channel, as described in multiple WhatsApp numbers.

If you are building a support product to sell to other companies, the path is different: WhatsApp inside your own system, one connection per customer, as shown on the WhatsApp API for helpdesks and WhatsApp API for SaaS pages. To understand each event that reaches the middleware, see the WhatsApp webhooks guide.

Frequently asked questions

Does D-API show up as a ready-made channel in Chatwoot?
No. Chatwoot offers the API channel inbox precisely for channels it does not know. D-API connects to that inbox through a middleware that talks to both APIs.
What does the middleware need to store?
The link between the customer’s phone number, the contact and the conversation in Chatwoot, and the reverse link between the conversation and the D-API number that serves it. Without it, it does not know where to log the incoming message or where to send the reply.
Do agents’ private notes reach the customer?
They should not. The Chatwoot webhook includes the private field, and the middleware only forwards messages with message_type outgoing and private false. Internal notes stay in Chatwoot.
Can I handle several numbers in the same Chatwoot?
Yes. The simplest design is one API channel inbox per D-API connection. Each inbox has its own callback URL, and the middleware uses it to know which number the reply should go out from.
Do image and audio messages work too?
They do, but they need more code. Inbound, the D-API webhook includes media_url for received media, which the middleware attaches to the message in Chatwoot. Outbound, agent attachments go to the D-API media send routes.

Try D-API's WhatsApp API

3-day trial with full access. No credit card, no lock-in.