What Typebot does and what it does not do on its own
Typebot is good at designing the conversation: questions, answer validation, variables, conditions. What it does not do is talk to a number connected by QR code. The WhatsApp integration that ships with Typebot is designed for Meta’s official platform, configured with your Meta account credentials.
D-API does not have a native Typebot connector either. So, to run the flow on a D-API number, the conversation goes through a third component. Being upfront about this saves you the frustration of looking for a "connect" button that does not exist.
The architecture: three pieces and two APIs
- The person writes on WhatsApp. D-API fires the
messages.receivedevent to your middleware’s URL. - The middleware checks whether that phone number already has a Typebot session. If not, it calls
POST /api/v1/typebots/YOUR_PUBLIC_ID/startChat. If it does, it callsPOST /api/v1/sessions/SESSION_ID/continueChatwith the received text. - Typebot responds with a
messagesarray (the bot bubbles) and theinputit expects next. - The middleware sends each bubble through D-API, in order, and stores the Typebot
sessionIdfor the same person’s next message.
On Typebot cloud, the base for these routes is https://typebot.io/api/v1. If you self-host Typebot, use your own domain. The flow must be published.
The middleware in code
A lean Node.js example, with the d-api-sdk SDK for sending and a Map standing in for storage. In production, replace the Map with Redis or a database, because process memory is lost on deploy and does not work with more than one instance:
import express from 'express'
import { DApi } from 'd-api-sdk'
const dapi = new DApi({ apiKey: process.env.DAPI_KEY })
const TYPEBOT = 'https://typebot.io/api/v1'
const sessions = new Map() // phone -> Typebot sessionId
const app = express()
app.use(express.json())
app.post('/webhook/dapi', async (req, res) => {
res.sendStatus(200) // answer fast; processing continues below
const { event, sessionId, data } = req.body
if (event !== 'messages.received' || data.fromMe || data.is_group) return
const phone = data.from.jid.split('@')[0] // e.g. 14155550123
const typebotSession = sessions.get(phone)
const url = typebotSession
? `${TYPEBOT}/sessions/${typebotSession}/continueChat`
: `${TYPEBOT}/typebots/${process.env.TYPEBOT_PUBLIC_ID}/startChat`
const reply = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: { type: 'text', text: data.message },
textBubbleContentFormat: 'markdown',
}),
}).then((r) => r.json())
if (reply.sessionId) sessions.set(phone, reply.sessionId)
for (const bubble of reply.messages ?? []) {
if (bubble.type !== 'text') continue
await dapi.messages.sendText({ sessionId, to: phone, text: bubble.content.markdown })
}
})
app.listen(3000)Requesting textBubbleContentFormat: 'markdown' makes Typebot return each bubble as plain text with markup, which is much easier to forward than the default rich format.
What the example above does not cover yet
The code shows the skeleton. A middleware that handles real usage also takes care of:
- Buttons and choices: when the Typebot
inputis a choice, turn the options into a list through the/api/v1/interactive/send/listroute or into numbered text. Details for each format are in WhatsApp buttons and lists. - Media: image, video or audio bubbles go to the matching media send routes, with the URL Typebot returns.
- End of flow: when Typebot no longer returns an
input, delete the stored session. Otherwise, that person’s next message lands in a finished flow. - Repeated messages: D-API may resend a webhook when your URL is slow or fails. Keep the
data.idof processed messages for a few minutes and ignore duplicates. - Handoff to a human: define a variable in the flow that, when set, makes the middleware stop calling Typebot and route the conversation to support.
Where the HTTP request block comes in
Inside the flow, Typebot has the HTTP request block, which calls any API mid-conversation, with URL, method, a body with variables in the {{Variable}} format and the option to save the response into variables. It does not replace the middleware, because it does not receive messages, but it is useful for side actions. An example: when the lead finishes qualification, the flow notifies the rep on WhatsApp.
POST https://api.d-api.cloud/api/v1/messages/send/text
Authorization: YOUR_API_KEY
Content-Type: application/json
{
"sessionId": "sales",
"to": "14155550199",
"text": "Qualified lead: {{Name}}, company {{Company}}, budget {{Budget}}."
}By default the block waits 10 seconds for a response, and the timeout can be adjusted in the advanced options. Since the key lives inside the block, restrict who can edit the flow.
Your own middleware or n8n
If your team already writes code, the middleware above becomes a small service next to your product. If not, you can build the same bridge in n8n: a Webhook receives the D-API event, an HTTP Request node calls Typebot and the native D-API node sends the replies.
Either way, the WhatsApp piece is the same D-API WhatsApp API, with per-instance isolation and a dedicated IP per connection. To compare this design with a bot built on a language model, see WhatsApp AI chatbot. For other automation uses, the WhatsApp API for automation page brings the options together, and the WhatsApp webhooks guide details the payload the middleware receives.
