WhatsApp

Connect WhatsApp Business calling to Voximplant scenarios
View as Markdown

For the complete documentation index, see llms.txt.

Overview

WhatsApp integration lets you process WhatsApp calls/messages with VoxEngine logic and route them to your Voice AI scenarios. Use the integration flow to connect Meta credentials, complete verification, and attach numbers.

WhatsApp integration overview

Prerequisites

  • Meta developer account with a WhatsApp Business app in developers.facebook.com.
  • WhatsApp Business phone number added in WhatsApp Manager and ready for verification.
  • Meta Cloud API credentials: Temporary Access Token and Phone Number ID.

Inbound

Full inbound setup walkthrough video

Use this step-by-step video to see the full setup in Meta and Voximplant.

Video link: WhatsApp Business Calling setup overview

Setup flow (Inbound)

1

Prepare WhatsApp Cloud API in Meta

In developers.facebook.com/apps, create a Business app, add WhatsApp, and open WhatsApp > API Setup. Keep your Temporary Access Token and Phone Number ID available. Meta API Setup

2

Verify and register the WhatsApp number on the Meta side

In WhatsApp Manager, complete verification and registration on the WhatsApp Cloud API side:

  1. In the Profile tab, click Send verification code. Send verification code

  2. Choose how to receive the code (SMS or phone call). Choose verification method

  3. Enter the received code and keep it for the registration step. Enter verification code

  4. Open the Certificate tab and wait until Display Name status becomes Approved. Display Name status

  5. After approval, register the phone number with the Meta Graph API request shown in the guide/modal. Phone registration step

  6. Refresh the phone numbers page and confirm the number status is Connected. Connected status

3

Create Voximplant application

In manage.voximplant.com, create or open an application for this WhatsApp inbound flow. Create Voximplant application

4

Create inbound scenario

Create your inbound scenario (for example whatsapp-inbound) and add the call handling logic. WhatsApp inbound scenario code

Minimal inbound WhatsApp scenario
1VoxEngine.addEventListener(AppEvents.CallAlerting, (event) => {
2 const call = event.call;
3 call.answer();
4 call.say("Hello, this WhatsApp call is connected to Voximplant.", {voice: VoiceList.Amazon.en_US_Joanna});
5 call.addEventListener(CallEvents.Disconnected, () => VoxEngine.terminate());
6});
5

Connect the WhatsApp number in Voximplant

Open WhatsApp numbers in your application and click Add WhatsApp number. WhatsApp numbers in manage.voximplant.com

Follow the modal instructions: WhatsApp configuration modal

You will need to execute the Meta Graph API requests via cURL.

Then copy the returned password into SIP password, set the WhatsApp number, and click Save. Save SIP password

6

Create routing rule

Create a routing rule and attach the inbound scenario. Open your application and go to Routing. Create a routing rule

Click Create / New rule. The default mask .* is fine to process all inbound calls. Attach scenarios to a routing rule

7

Attach the WhatsApp number to your Application

Select the available WhatsApp number and attach it to the current application.

8

Test inbound calling

Now you can place a call to your WhatsApp Business number and see it hit your VoxEngine scenario!

Outbound specifics

Outbound requires the same setup as inbound. Then you can use VoxEngine.callWhatsappUser() to initiate outbound calling to WhatsApp users from one of your WhatsApp business phone numbers from a scenario.

Outbound WhatsApp
1const call = VoxEngine.callWhatsappUser({
2 number: "+15551234567",
3 callerid: "15557654321"
4});

Outbound video walkthrough:

Video link: Outbound WhatsApp calling walkthrough

More information:

Multi-modal simultaneous voice & messaging support

The WhatsApp integration supports both calls and messages, so you can create multi-modal scenarios that handle voice and text in the same flow. For example, you can answer the call, send a welcome message, then continue with voice prompts and responses.

Architecture

This follows a similar setup procedure as above, but requires an additional server to proxy messages.

WhatsApp multi-modal architecture

Walkthrough and demo

See here for a quick walkthrough and demo of this capability: Video link: Inbound WhatsApp calling demo

Example code

VoxEngine code sample for the WhatsApp multi-modal voice + text flow using gpt-realtime-2.1:

voxengine_openai_ga.js
1require(Modules.ApplicationStorage);
2require(Modules.OpenAI);
3
4let sessionUrl = null, connected = false, cid = null, realtimeAPIClient = undefined;
5
6const OPENAI_API_KEY = VoxEngine.getSecretValue("OPENAI_API_KEY");
7const MODEL = "gpt-realtime-2.1";
8const WA_PROXY_URL = "https://waproxy.ngrok.app/webhook";
9
10const onWebSocketClose = (event) => {
11 Logger.write("===ON_WEB_SOCKET_CLOSE==");
12 Logger.write(JSON.stringify(event));
13 VoxEngine.terminate();
14};
15
16VoxEngine.addEventListener(AppEvents.Started, (appEvent) => {
17 sessionUrl = appEvent.accessSecureURL;
18});
19
20VoxEngine.addEventListener(AppEvents.HttpRequest, (appEvent) => {
21 Logger.write("Inbound Http request");
22 try {
23 let data = JSON.parse(appEvent.content);
24 if (data.text?.body != undefined) {
25
26 const item = {
27 "item": {
28 "type": "message",
29 "role": "user",
30 "content": [
31 {
32 "type": "input_text",
33 "text": data.text.body,
34 },
35 ],
36 },
37 };
38
39 // Client events are processed in order, so the item is in the
40 // conversation before the response is generated
41 realtimeAPIClient.conversationItemCreate(item);
42 realtimeAPIClient.responseCreate({});
43 }
44
45 } catch (err) {
46 Logger.write(JSON.stringify(err));
47 }
48 return "OK";
49});
50
51VoxEngine.addEventListener(AppEvents.CallAlerting, async ({callerid, call}) => {
52 cid = callerid;
53 const realtimeAPIClientParameters = {
54 model: MODEL,
55 apiKey: OPENAI_API_KEY,
56 type: OpenAI.RealtimeAPIClientType.REALTIME,
57 onWebSocketClose,
58 };
59
60 call.answer();
61 try {
62 realtimeAPIClient = await OpenAI.createRealtimeAPIClient(realtimeAPIClientParameters);
63 const session_update = {
64 "session": {
65 "type": "realtime",
66 "model": MODEL,
67 "instructions": `Your name is Voxy, you're a friendly and fun guy. You speak English only. You have to collect person's name, company he/she works at and his/her email. Call the 'createProfile' function whenever you learn all information including name, company and email address. You MUST NEVER mention the tools/functions to the user. You speak English ONLY, don't switch to any other language. Always continue the conversation after the user answers.`,
68 "audio": {
69 "input": {
70 "transcription": {
71 "model": "gpt-4o-transcribe",
72 "language": "en",
73 },
74 "turn_detection": {
75 "type": "semantic_vad",
76 "eagerness": "auto",
77 "interrupt_response": true,
78 },
79 },
80 "output": {
81 "voice": "cedar",
82 },
83 },
84 "tools": [
85 {
86 "type": "function",
87 "name": "createProfile",
88 "description": "Save contact information of a user for the purpose of creating/updating profile information.",
89 "parameters": {
90 "type": "object",
91 "properties": {
92 "name": {
93 "type": "string",
94 "description": "The user's name",
95 },
96 "emailAddress": {
97 "type": "string",
98 "description": "The user's work/business email address.",
99 },
100 "organization": {
101 "type": "string",
102 "description": "The name of the company/organization where the user works.",
103 },
104 },
105 "required": ["name", "organization", "emailAddress"],
106 },
107 },
108 ],
109 "tool_choice": "auto",
110 },
111 };
112 realtimeAPIClient.sessionUpdate(session_update);
113 VoxEngine.sendMediaBetween(call, realtimeAPIClient);
114 connected = true;
115 const response = {};
116 realtimeAPIClient.responseCreate(response);
117
118 // Interruptions support: clear the media buffer in case of OpenAI's VAD detected speech input
119 realtimeAPIClient.addEventListener(OpenAI.RealtimeAPIEvents.InputAudioBufferSpeechStarted, () => {
120 Logger.write("===BARGE-IN: OpenAI.InputAudioBufferSpeechStarted===");
121 if (realtimeAPIClient) realtimeAPIClient.clearMediaBuffer();
122 });
123
124 realtimeAPIClient.addEventListener(OpenAI.RealtimeAPIEvents.ResponseDone, async (event) => {
125 // Logger.write("RESPONSE DONE");
126 // Logger.write(JSON.stringify(event));
127 // Check the function name and act accordingly
128 if (event.data.payload?.response?.output[0].type == "function_call" && event.data.payload?.response?.output[0].name == "createProfile") {
129 try {
130 let args = JSON.parse(event.data.payload.response.output[0].arguments);
131 if (args.name == "" || args.emailAddress == "" || args.organization == "") return;
132 Logger.write("Profile created, sending info to WhatsApp");
133 const obj = {
134 entry: [
135 {
136 changes: [
137 {
138 value: {
139 messages: [
140 {
141 from: cid,
142 type: "voiceai",
143 text: {
144 body: "Name: " + args.name + ", Email: " + args.emailAddress + ", Company: " + args.organization,
145 },
146 },
147 ],
148 },
149 field: "messages",
150 },
151 ],
152 },
153 ],
154 };
155 Logger.write(JSON.stringify(obj));
156 await Net.httpRequestAsync(WA_PROXY_URL, {
157 method: "POST",
158 postData: JSON.stringify(obj),
159 enableSystemLog: true,
160 headers: [
161 "Content-Type: application/json",
162 ],
163 });
164 const response = {};
165 realtimeAPIClient.responseCreate(response);
166 } catch (err) {
167 Logger.write(err);
168 }
169 // https://waproxy.ngrok.app/webhook
170 }
171 });
172
173 } catch (error) {
174 Logger.write("===SOMETHING_WENT_WRONG===");
175 Logger.write(error);
176 VoxEngine.terminate();
177 }
178
179 call.record({hd_audio: true, stereo: true});
180 try {
181 ApplicationStorage.put("WAB_" + callerid, sessionUrl, 60 * 90); // assuming that the call session wouldn't last longer than 1.5 hours
182 } catch (e) {
183 Logger.write("ApplicationStorage error: " + JSON.stringify(e));
184 }
185
186 call.addEventListener(CallEvents.Disconnected, () => {
187 if (realtimeAPIClient) realtimeAPIClient.close();
188 connected = false;
189 try {
190 ApplicationStorage.remove("WAB_" + callerid);
191 } catch (e) {
192 Logger.write("ApplicationStorage error: " + JSON.stringify(e));
193 }
194 VoxEngine.terminate();
195 });
196
197
198});
199
200VoxEngine.addEventListener(AppEvents.Terminating, () => {
201 if (connected) {
202 try {
203 ApplicationStorage.remove("WAB_" + cid);
204 } catch (e) {
205 Logger.write("ApplicationStorage error: " + JSON.stringify(e));
206 }
207 }
208});

Node.js proxy server code: