clairton / unoapi-cloud

Unofficial Whatsapp Cloud Api - Same format as whastapp cloud api, but without spent
GNU General Public License v3.0
165 stars 54 forks source link
baileys whatsapp whatsapp-api whatsapp-cloud-api whatsapp-web
[![Whatsapp Group](https://img.shields.io/badge/Group-WhatsApp-%2322BC18)](https://chat.whatsapp.com/FZd0JyPVMLq94FHf59I8HU) [![License](https://img.shields.io/badge/license-GPL--3.0-orange)](./LICENSE) [![Support](https://img.shields.io/badge/Donation-picpay-green)](https://app.picpay.com/user/clairton.rodrigo) [![Support](https://img.shields.io/badge/Buy%20me-coffe-orange)](https://www.buymeacoffee.com/clairton)

Unoapi Cloud

An implementation of Baileys(https://github.com/WhiskeySockets/Baileys) as RESTful API service with multi device support with a Whatsapp Cloud API format https://developers.facebook.com/docs/whatsapp/cloud-api.

The media files are saved in file system at folder data with the session or in s3 or compatible and redis.

Read qrcode or config

Go to http://localhost:9876/session/XXX, when XXX is your phone number, by example http://localhost:9876/session/5549988290955. When disconnect whatsapp number this show the qrcode, read to connect, unoapi response with auth token, save him. When already connect, they show the number config saved in redis, you cloud update, put the auth token and save.

The qrcode is send to configured webhook to, you can read in chatwoot inbox, in created chat with de same number of connection.

Qrcode with websocket

Use the endpoint /ws and listen event broadcast, the object with type qrcode has a content attribute with de base64 url of qrcode

import { io } from 'socket.io-client';
const socket = io('http://localhost:9876/ws', { path: '/ws' });
socket.on('broadcast', data => {
  console.log('broadcast', data);
});

Send a Message

The payload is based on https://developers.facebook.com/docs/whatsapp/cloud-api/webhooks/components#messages-object

To send a message

curl -i -X POST \
http://localhost:9876/v15.0/5549988290955/messages \
-H 'Content-Type: application/json' \
-H 'Authorization: 1' \
-d '{
  "messaging_product": "whatsapp",
  "to": "5549988290955",
  "type": "text",
  "text": {
    "body": "hello"
  } 
}'

To send a message to group

curl -i -X POST \
http://localhost:9876/v15.0/5549988290955/messages \
-H 'Content-Type: application/json' \
-d '{
  "messaging_product": "whatsapp",
  "to": "120363040468224422@g.us",
  "type": "text",
  "text": {
    "body": "hello"
  }
}'

Media

To test media

curl -i -X GET \
http://localhost:9876/v15.0/5549988290955/3EB005A626251D50D4E4 \
-H 'Content-Type: application/json'

This return de url and request this url like

curl -i -X GET \
http://locahost:9876/download/v13/5549988290955/5549988290955@s.whatsapp.net/48e6bcd09a9111eda528c117789f8b62.png \
-H 'Content-Type: application/json'

To send media

https://developers.facebook.com/docs/whatsapp/cloud-api/guides/send-messages#media-messages

curl -i -X POST \
http://localhost:9876/v15.0/5549988290955/messages \
-H 'Content-Type: application/json' \
-d '{
  "messaging_product": "whatsapp",
  "to": "5549988290955",
  "type": "image",
  "image": {
    "link" : "https://github.githubassets.com/favicons/favicon-dark.png"
  }
}'

Webhook Events

Webhook Events like this https://developers.facebook.com/docs/whatsapp/cloud-api/webhooks/payload-examples

Message status update on this https://developers.facebook.com/docs/whatsapp/cloud-api/webhooks/payload-examples#message-status-updates

To turn possible work with group, we add three fields(group_id, group_subject and group_picture) in message beside cloud api format if IGNORE_GROUP_MESSAGES is false. Unoapi put fieldpicture in profile.

{
  "object": "whatsapp_business_account",
  "entry": [{
      "id": "WHATSAPP_BUSINESS_ACCOUNT_ID",
      "changes": [{
          "value": {
              "messaging_product": "whatsapp",
              "metadata": {
                  "display_phone_number": PHONE_NUMBER,
                  "phone_number_id": PHONE_NUMBER_ID
              },
              "contacts": [{
                  "profile": {
                    "name": "NAME",
                    "picture": "url of image" // extra field of whatsapp cloud api oficial
                  },
                  "group_id": "123345@g.us", // extra field of whatsapp cloud api oficial
                  "group_subject": "Awesome Group", // extra field of whatsapp cloud api oficial
                  "group_picture": "url of image", // extra field of whatsapp cloud api oficial
                  "wa_id": PHONE_NUMBER
                }],
              "messages": [{
                  "from": PHONE_NUMBER,
                  "id": "wamid.ID",
                  "timestamp": TIMESTAMP,
                  "text": {
                    "body": "MESSAGE_BODY"
                  },
                  "type": "text"
                }]
          },
          "field": "messages"
        }]
  }]
}

Error Messages

Messages failed with this https://developers.facebook.com/docs/whatsapp/cloud-api/webhooks/payload-examples#status--message-failed

Custom errors sound append this codes https://developers.facebook.com/docs/whatsapp/cloud-api/support/error-codes with:

Up for development

Copy .env.example to .env an set your config

A docker-compose.yml file is available:

docker compose up

Visit http://localhost:9876/ping wil be render a "pong!"

Up for production

A docker-compose.yml example for production:

version: '3'

services:
  app:
    image: clairton/unoapi-cloud:latest
    volumes:
      - ./data:/home/u/app/data
    ports:
      - 9876:9876
    deploy:
      restart_policy:
        condition: on-failure

Run docker compose up

Visit http://localhost:9876/ping wil be render a "pong!"

Start options

yarn start up a single server and save session and media file in filesystem

yarn cloud up a single server and save message in redis and message broker rabbitmq

yarn web e yarn worker up a web and worker with redis and rabbitmq

Config Options

Config with Environment Variables

Create a .envfile and put configuration if you need change default value:

WEBHOOK_URL_ABSOLUTE=the webhook absolute url, not use this if already use WEBHOOK_URL
WEBHOOK_URL=the webhook url, this config attribute put phone number on the end, no use if use WEBHOOK_URL_ABSOLUTE
WEBHOOK_TOKEN=the webhook header token
WEBHOOK_HEADER=the webhook header name
WEBHOOK_SESSION=webhook to send events of type OnStatus and OnQrCode
WEBHOOK_TIMEOUT_MS=webhook request timeout, default 5000 ms
WEBHOOK_SEND_NEW_MESSAGES=true, send new messages to webhook, caution with this, messages will be duplicated, default is false
WEBHOOK_SEND_GROUP_MESSAGES=true, send group messages to webhook, default is true
WEBHOOK_SEND_OUTGOING_MESSAGES=true, send outgoing messages to webhook, default is true
BASE_URL=current base url to download medias
PORT=the http port
IGNORE_GROUP_MESSAGES=false to send group messages received in socket to webhook, default true
IGNORE_BROADCAST_STATUSES=false to send stories in socket to webhook, default true
IGNORE_STATUS_MESSAGE=false to send stories in socket to webhook, default true
IGNORE_BROADCAST_MESSAGES=false to send broadcast messages in socket to webhook, default false
IGNORE_HISTORY_MESSAGES=false to import messages when connect, default is true
IGNORE_OWN_MESSAGES=false to send own messages in socket to webhook, default true
IGNORE_YOURSELF_MESSAGES=true to ignore messages for yourself, default is true, possible loop if was false
COMPOSING_MESSAGE=true enable composing before send message as text length, default false
REJECT_CALLS=message to send when receive a call, default is empty and not reject
REJECT_CALLS_WEBHOOK=message to send webook when receive a call, default is empty and not send, is deprecated, use MESSAGE_CALLS_WEBHOOK
MESSAGE_CALLS_WEBHOOK=message to send webook when receive a call, default is empty and not send
SEND_CONNECTION_STATUS=true to send all connection status to webhook, false to send only important messages, default is true
BASE_STORE=dir where save sessions, medias and stores. Defaul is ./data
IGNORE_DATA_STORE=ignore save/retrieve data(message, contacts, groups...)
AUTO_CONNECT=true, auto connect on start service
AUTO_RESTART_MS=miliseconds to restart connection, default is 0 and not auto restart
THROW_WEBHOOK_ERROR=false send webhook error do self whatsapp, default is false, if true throw exception
NOTIFY_FAILED_MESSAGES=true send message to your self in whatsapp when message failed and enqueued in dead queue
LOG_LEVEL=log level, default warn
UNO_LOG_LEVEL=uno log level. default LOG_LEVEL
SEND_REACTION_AS_REPLY=true to send reactions as replay, default false
SEND_PROFILE_PICTURE=true to send profile picture users and groups, default is true
UNOAPI_RETRY_REQUEST_DELAY_MS=retry delay in miliseconds when decrypt failed, default is 1_000(a second)
UNOAPI_DELAY_AFTER_FIRST_MESSAGE_MS=to service had time do create contact and conversation before send next messages, default 0
UNOAPI_DELAY_AFTER_FIRST_MESSAGE_WEBHOOK_MS=to service had time do create contact and conversation in first message after unoapi up, before send next messages, default 0
UNOAPI_DELAY_BETWEEN_MESSAGES_MS=to not duplicate timestamp message. default 0
PROXY_URL=the socks proxy url, default not use
CLEAN_CONFIG_ON_DISCONNECT=true to clean all saved redis configurations on disconnect number, default is false
CONFIG_SESSION_PHONE_CLIENT=Unoapi Name that will be displayed on smartphone connection
CONFIG_SESSION_PHONE_NAME=Chrome Browser Name = Chrome | Firefox | Edge | Opera | Safari
WHATSAPP_VERSION=Version of whatsapp, default to local Baileys version.
CONSUMER_TIMEOUT_MS=miliseconds in timeout for consume job, default is 30000
MESSAGE_CHECK_WAAPP=message to send webwook when uno fails on reading content. default '🕒 Não foi possível ler a mensagem. Peça para enviar novamente ou abra o Whatsapp no celular.'
ONLY_HELLO_TEMPLATE=true sets hello template as the only default template, default false.

Bucket env to config assets media compatible with S3, this config can't save in redis:

STORAGE_BUCKET_NAME
STORAGE_ACCESS_KEY_ID
STORAGE_SECRET_ACCESS_KEY
STORAGE_REGION
STORAGE_ENDPOINT
STORAGE_FORCE_PATH_STYLE
STORAGE_TIMEOUT_MS

Config connection to redis to temp save messages and rabbitmq broker, this config can't save in redis too.

AMQP_URL
REDIS_URL

Config with redis

The .env can be save one configm, but on redis use different webhook by session number, to do this, save the config json with key format unoapi-config:XXX, where XXX is your whatsapp number.

{
  "authToken": "xpto",
  "rejectCalls":"Reject Call Text do send do number calling to you",
  "rejectCallsWebhook":"Message send to webhook when receive a call",
  "ignoreGroupMessages": true,
  "ignoreBroadcastStatuses": true,
  "ignoreBroadcastMessages": false,
  "ignoreHistoryMessages": true,
  "ignoreOwnMessages": true,
  "ignoreYourselfMessages": true,
  "sendConnectionStatus": true,
  "composingMessage": false,
  "sessionWebhook": "",
  "autoConnect": false,
  "autoRestartMs": 3600000,
  "retryRequestDelayMs": 1000,
  "throwWebhookError": false,
  "webhooks": [
    {
      "url": "http://localhost:3000/whatsapp/webhook",
      "token": "kslflkhlkwq",
      "header": "api_access_token",
      "sendGroupMessages": false,
      "sendGroupMessages": false,
      "sendNewMessages": false,
    }
  ],
  "ignoreDataStore": false
}

PS: After update JSON, restart de docker container or service

Save config with http

To create a session with http send a post with config in body to http://localhost:9876/v15.0/:phone/register, change :phone by your phone session number and put content of env UNOAPI_AUTH_TOKEN in Authorization header:

curl -i -X POST \
http://localhost:9876/v17.0/5549988290955/register \
-H 'Content-Type: application/json' \
-H 'Authorization: 1' \
-d '{ 
  "ignoreOwnMessages": false
}'

Delete config and session with http

To remover a session with http send a post to http://localhost:9876/v15.0/:phone/deregister, change :phone by your phone session number and put content of env UNOAPI_AUTH_TOKEN in Authorization header:

curl -i -X POST \
http://localhost:9876/v17.0/5549988290955/deregister \
-H 'Content-Type: application/json' \
-H 'Authorization: 1' 

Get a session config

curl -i -X GET \
http://localhost:9876/v15.0/5549988290955 \
-H 'Content-Type: application/json' \
-H 'Authorization: 1'

List the sessions configs

curl -i -X GET \
http://localhost:9876/v15.0/phone_numbers \
-H 'Content-Type: application/json' \
-H 'Authorization: 1'
{
  "authToken": "xpto",
  "rejectCalls":"Reject Call Text do send do number calling to you",
  "rejectCallsWebhook":"Message send to webhook when receive a call",
  "ignoreGroupMessages": true,
  "ignoreBroadcastStatuses": true,
  "ignoreBroadcastMessages": false,
  "ignoreHistoryMessages": true,
  "ignoreOwnMessages": true,
  "ignoreYourselfMessages": true,
  "sendConnectionStatus": true,
  "composingMessage": false,
  "sessionWebhook": "",
  "autoConnect": false,
  "autoRestartMs": 3600000,
  "retryRequestDelayMs": 1000,
  "throwWebhookError": false,
  "webhooks": [
    {
      "url": "http://localhost:3000/whatsapp/webhook",
      "token": "kslflkhlkwq",
      "header": "api_access_token"
    }
  ],
  "ignoreDataStore": false
}

Templates

UnoAPI's default definition has 4 templates: hello, unoapi-bulk-report, unoapi-webhook and unoapi-config. UnoAPI can be configured to only present the hello template as default, check the ONLY_HELLO_TEMPLATE variable to obtain this behavior

The templates for each Session (PHONE_NUMBER) can be be customized, saving in ${BASE_STORE}/${PHONE_NUMBER}/templates.json , or when use redis with key unoapi-template:${PHONE_NUMBER}. The json format is:

[
  {
    "id": 1,
    "name": "hello",
    "status": "APPROVED",
    "category": "UTILITY",
    "components": [
      {
        "text": "{{hello}}",
        "type": "BODY",
        "parameters": [
          {
            "type": "text",
            "text": "hello",
          },
        ],
      },
    ],
  }
]

Save templates with http

To edit (by id) or add a template with http send a post with the template in body to http://localhost:9876/v15.0/:phone/templates, change :phone by your phone session number and put content of env UNOAPI_AUTH_TOKEN in Authorization header:

curl -i -X POST \
http://localhost:9876/v17.0/5549988290955/templates \
-H 'Content-Type: application/json' \
-H 'Authorization: 1' \
-d '{"id":1,"name":"hello","status":"APPROVED","category":"UTILITY","components":[{"text":"{{hello}}","type":"BODY","parameters":[{"type":"text","text":"hello"}]}]}'

PS: After update JSON, restart de docker container or service

Examples

Docker compose with chatwoot

Docker compose with chatwoot and unoapi inbox

Docker compose with unoapi

Docker compose with chatwoot and unoapi together

Typebot

Install as Systemctl

Install nodejs 21 as https://nodejs.org/en/download/package-manager and Git

mkdir /opt/unoapi && cd /opt/unoapi

git clone git@github.com:clairton/unoapi-cloud.git .

npm install

npm build

cp .env.example .env && vi .env

WEBHOOK_URL=http://chatwoot_addres/webhooks/whatsapp
WEBHOOK_TOKEN=chatwoot token
BASE_URL=https://unoapi_address
BASE_STORE=/opt/unoapi/data
WEBHOOK_HEADER=api_access_token

And other .env you desire

chown -R $(whoami) ./data/sessions && chown -R $(whoami) ./data/stores && chown -R $(whoami) ./data/medias

vi /etc/systemd/system/unoapi.service or systemctl edit --force --full unoapi.service

And put

[Unit]
Description=Unoapi
ConditionPathExists=/opt/unoapi/data
After=network.target

[Service]
ExecStart=/usr/bin/node dist/index.js
WorkingDirectory=/opt/unoapi
CPUAccounting=yes
MemoryAccounting=yes
Type=simple
Restart=on-failure
TimeoutStopSec=5
RestartSec=5

[Install]  
WantedBy=multi-user.target

Run

systemctl daemon-reload && systemctl enable unoapi.service && systemctl start unoapi.service

To show logs journalctl -u unoapi.service -f

Postman collection

Postman Collection

Caution with whatsapp web connection

More then 14 days without open app in smartphone, the connection with whatsapp web is invalidated and need to read a new qrcode.

Future providers

Current lib is baileys, to other libraries implement subscribe:

https://github.com/NaikAayush/whatsapp-cloud-api https://github.com/green-api/whatsapp-api-client-golang

Legal

Note

I can't guarantee or can be held responsible if you get blocked or banned by using this software. WhatsApp does not allow bots using unofficial methods on their platform, so this shouldn't be considered totally safe.

Released under the GPLv3 License.

WhatsApp Group

https://chat.whatsapp.com/FZd0JyPVMLq94FHf59I8HU

Need More

Mail to comercial@unoapi.cloud

Donate to the project.

Pix: 0e42d192-f4d6-4672-810b-41d69eba336e


PicPay


Buy Me A Coffee