Is Blueticks: WhatsApp Scheduler, Marketing Campaigns & AI Agent safe?
Blueticks keeps a persistent connection to its MQTT broker and runs server-issued commands that read your WhatsApp contacts, chats, and messages.
On WhatsApp Web, Blueticks injects a page script that hooks WhatsApp's internal data store to read contacts, chat lists, and full message bodies. It maintains a constant WebSocket/MQTT connection to mqtt.blueticks.co keyed to your account ID, where the vendor's server can remotely request that data (get_contacts, get_chats, get_messages) and have results published back, or instruct the extension to send messages from your account. The extension's background worker also exposes a generic fetch-any-URL proxy to allowed origins and reports an install event with marketing-attribution cookies to Google Analytics.
Who publishes itSerenix LTD - no other listings under this identity
Serenix LTD - no other listings under this identity
What this publisher told the store about itself, and the other listings that told it the same thing.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Remote command can send WhatsApp messages
The extension subscribes to a Blueticks MQTT broker for the signed-in user.
Code shows a backend request can reach the function sending a WhatsApp message from the active session.
No live MQTT command captured; evidence is the code path.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-940
- Source
- Dynamic sandbox
A Blueticks backend request reaches the extension over the user-specific MQTT request topic.
The user must have WhatsApp Web open for the WhatsApp-interface code path to run.
The extension forwards the request method and parameters into the WhatsApp-interface tool path.
Behavioral verification confirmed this path can reach the WhatsApp message-sending function without a separate authorization check in the extension.
WebSocket MQTT connection; the code subscribes to users/{userId}/requests/+ and clients/{currentClientId}/requests/+ after connection.
- User request topicusers/5521432/requests/req-20260712-01
This routes backend requests to the extension instance associated with your signed-in account.
- Command namesend_message_from_whatsapp_interface_actions
This chooses which WhatsApp-interface action the extension runs.
- Message parameterscontactList[0].id=15551234567@c.us; message=Reminder for today
These values can provide the recipient and message content that the extension passes into WhatsApp Web.
MQTT requests are forwarded to the WhatsApp-interface tool path
Deobfuscated app.js request dispatcher
deobfuscated/app.jssubscribeToTopics(){if(!this.client||!this.userId)return;[`users/${this.userId}/downstream/+`,`users/${this.userId}/requests/+`,`clients/${this.currentClientId}/requests/+`,"global/broadcast/+",`users/${this.userId}/presence/#`].forEach(e=>{this.client?.subscribe(e,t=>{t||(this.log(`[MQTT] Subscribed to ${e}`),this.subscribedTopics.add(e))})})}handleMessage(e,t){if(this.log(`[MQTT] Received message on topic ${e}:`,t),e.includes("/requests/"))return void this.handleRequest(e,t);if(e.endsWith("/client-events"))return void this.handleClientEvents(e,t);if(e.includes("/presence/"))return void this.handlePresenceMessage(e,t);const n=this.extractEventTypeFromTopic(e);if(n){if(this.log(`[MQTT] Mapped topic ${e} to event type: ${n}`),"global-broadcast"===n){const e=t.eventType;if(e){this.log(`[MQTT] Global broadcast event type: ${e}`);const n=this.convertToSocketIOFormat(t.data||t,e),r=this.messageHandlers.get(e);r&&(this.log(`[MQTT] Dispatching global broadcast ${e} event to handler`),r(n))}return}const r=this.convertToSocketIOFormat(t,n),a=this.messageHandlers.get(n);a&&(this.log(`[MQTT] Dispatching ${n} event to handler`),a(r))}}handleRequest(e,t){this.log(`[MQTT] Handling request on topic ${e}:`,t);const n=e.split("/");let r;if("users"===n[0]&&"requests"===n[2])r=n[3];else{if("clients"!==n[0]||"requests"!==n[2])return;r=n[3],this.log(`[MQTT] Received client-specific request for ${n[1]}`)}r&&t.command&&this.processRequest(r,t.command,t.params||{})}async processRequest(e,t,n){const r=void 0!==n?.limit&&-1===Number(n.limit),a=Date.now();ds("WA_TAB_MQTT_REQUEST_RECEIVED",{requestId:e,command:t});try{if(this.log(`[MQTT] Processing request ${e}: ${t}`),"ping"===t)return void this.publishResponse(e,{success:!0,data:{message:"pong",timestamp:(new Date).toISOString(),engineType:(0,Zo.yS)(),clientId:this.currentClientId}});const i=n&&"string"==typeof n._engineCmdId?n._engineCmdId:null;if(i)try{this.backendActionsClient||(this.backendActionsClient=(0,cs.y)());const t=await this.backendActionsClient.checkEngineCmdLock({engineCmdId:i});if(t&&!1===t.acquired)return void this.publishResponse(e,{success:!0,data:{deduplicated:!0,lockId:i,reason:"engine-cmd-lock"}})}catch(e){e instanceof Error?e.message:String(e)}if(!(0,Zo.JH)())return void this.log(`Silently ignoring ${t} request in non-WhatsApp Web context (letting WhatsApp client respond)`);const o=n=>{ds("WA_TAB_MQTT_RESPONSE_PUBLISH",{requestId:e,command:t,durationMs:Date.now()-a,success:!!n?.success,error:n?.success?void 0:n?.error}),this.publishResponse(e,n)};if("insertOnlyIfMissing"===t){const e={path:"insertOnlyIfMissing",method:t,entity:n};return void await this.handleInsertOnlyIfMissingRequest(e,o)}const s={path:"whatsapp-interface",method:t,entity:{params:n,owner:this.userId}};if(r){Date.now();await(0,os.$f)(s,o,this.log.bind(this))}else await(0,os.$f)(s,o,this.log.bind(this))}catch(t){this.publishResponse(e,{success:!1,error:t?.message||"Unknown error"})}}The WhatsApp send helper passes the userMessage object to sendMessageThunkTool
Deobfuscated app.js send helper
deobfuscated/app.jsstatic async sendMessageViaWhatsapp(e){return(0,r.XL)(a.f_.sendMessageThunkTool,{userMessage:e},{timeoutOverride:6e4})}}},57991(e,t,n){"use strict";n.d(t,{$:()=>o,O:()=>s});var r=n(24259),a=n(52732);function i(e,t={}){(0,a.G)({eventType:e,source:"client",...t})}function o(e,t="Server tool request failed"){if(!e)return t;if("string"==typeof e&&e.trim())return e;if("string"==typeof e?.message&&e.message.trim())return e.message;if("string"==typeof e?.error&&e.error.trim())return e.error;if("string"==typeof e?.data?.message&&e.data.message.trim())return e.data.message;if("string"==typeof e?.data?.error&&e.data.error.trim())return e.data.error;if("string"==typeof e?.data?.error?.message&&e.data.error.message.trim())return e.data.error.message;if("string"==typeof e?.response?.data?.message&&e.response.data.message.trim())return e.response.data.message;if("string"==typeof e?.response?.data?.error&&e.response.data.error.trim())return e.response.data.error;if("string"==typeof e?.response?.data?.error?.message&&e.response.data.error.message.trim())return e.response.data.error.message;try{const t=JSON.stringify(e);if(t&&"{}"!==t)return t}catch{}const n=String(e);return n&&"[object Object]"!==n?n:t}async function s(e,t){const n={};try{const e=new URLSearchParams(window.location.search),t=e.get("supportedUserId"),r=e.get("workspaceId");t&&(n.supportedUserId=t),r&&(n.supportedWorkSpaceId=r)}catch{}const a=Date.now();i("SERVER_TOOL_REQUEST_START",{tool:e});try{const o=await(0,r.H)("post",`/actions?cmd=${encodeURIComponent(e)}`,t||{},{headers:n});return i("SERVER_TOOL_REQUEST_END",{tool:e,durationMs:Date.now()-a,success:!0}),o&&"object"==typeof o&&"success"in o&&("data"in o||"error"in o)?o:{success:!0,data:o}}catch(t){const n=o(t);return i("SERVER_TOOL_REQUEST_END",{tool:e,durationMs:Date.now()-a,success:!1,error:n}),{success:!1,error:{code:"SERVER_ERROR",message:n}}}}Deobfuscated app.js send result path
deobfuscated/app.jsconst e=await(0,u.wj)(A.W.sendMessageViaWhatsapp(n),"sendMessageViaWhatsapp",v);if((0,i.w2)(e)){const t=e.error,a="string"==typeof t?t:String(t??"unknown");return(0,o.lV)(e.errorType??"PAGE_HANDLER_THREW",`${a} (userMessageId=${n._id}, chatId=${r})`)}const t=e?.id??"";return t?(0,o._x)({id:t,chatId:r,userMessageId:n._id,messageId:t,waMessageKey:t,response:e,waMeInfo:g}):(0,o.lV)("NO_MESSAGE_ID",`Send response did not include a message id (userMessageId=${n._id}, chatId=${r})`)}catch(e){return(0,o.lV)("SEND_THREW",`${e?.message||String(e)} (userMessageId=${n._id}, chatId=${r})`)}}const x=(0,p.zD)("sendScheduleMessageThunk",async({userMessage:e,isSendMessageNow:t},n)=>{const p=n.dispatch;if(!e._id&&e.id&&(e._id=e.id),!(0,a.JH)())return(0,f.O)(o.f_.sendMessageThunkTool,{userMessage:e,isSendMessageNow:t});- mqtt.blueticks.co
Blueticks MQTT broker that delivers user-specific request messages to the extension.
- stg-mqtt.blueticks.co
Broker hostname present in the shipped app configuration for MQTT-over-WebSocket connections.
- web.whatsapp.com
WhatsApp Web page where the extension content script can invoke the message-sending workflow.
Where it sends data
Destinations our analysis observed Blueticks contacting. Sending data somewhere is not a finding on its own - an extension that syncs your settings has to talk to its own server - but it is where your data can go, and who else it goes to.
- www.google-analytics.comwidely used
Blueticks sends data to www.google-analytics.com. A widely used service: 346 other extensions we have analysed send data here.
- app.blueticks.co
Blueticks sends data to app.blueticks.co. No other extension we have analysed sends data here.
- blueticks.co
Blueticks sends data to blueticks.co. No other extension we have analysed sends data here.
- mqtt.blueticks.co
Blueticks sends data to mqtt.blueticks.co. No other extension we have analysed sends data here.