Cobalt

Whatsapp4j has been renamed to Cobalt to comply with an official request coming from Whatsapp. The repository's history was cleared to comply with this request, but keep in mind that the project has been actively developed for over two years. To be clear, this library is not affiliated with Whatsapp LLC in any way. This is a personal project that I maintain in my free time

What is Cobalt

Cobalt is a library built to interact with Whatsapp. It can be used with:

  1. Whatsapp Web (Companion)
  2. Whatsapp Mobile (Personal and Business)

Donations

If you like my work, you can become a sponsor here on GitHub or tip me through:

I can also work on sponsored features and/or projects!

Java version

This library requires at least Java 21.

GraalVM native compilation is supported!

Breaking changes policy

Until the library doesn't reach release 1.0, there will be major breaking changes between each release. This is needed to finalize the design of the API. After this milestone, breaking changes will be present only in major releases.

Can this library get my device banned?

While there is no risk in using this library with your main account, keep in mind that Whatsapp has anti-spam measures for their web client. If you add a participant from a brand-new number to a group, it will most likely get you banned. If you compile the library yourself, don't run the CI on a brand-new number, or it will get banned for spamming too many requests(the CI has to test that all the library works). In short, if you use this library without a malicious intent, you will never get banned.

How to install

Maven

<dependency>
    <groupId>com.github.auties00</groupId>
    <artifactId>cobalt</artifactId>
    <version>0.0.9</version>
</dependency>

Gradle

Javadocs & Documentation

Javadocs for Cobalt are available here. The documentation for this project reaches most of the publicly available APIs(i.e. public members in exported packages), but sometimes the Javadoc may be incomplete or some methods could be absent from the project's README. If you find any of the latter, know that even small contributions are welcomed!

How to contribute

As of today, no additional configuration or artifact building is needed to edit this project. I recommend using the latest version of IntelliJ, though any other IDE should work. If you are not familiar with git, follow these short tutorials in order:

  1. Fork this project
  2. Clone the new repo
  3. Create a new branch
  4. Once you have implemented the new feature, create a new merge request

Check the frida module to understand how I go about reversing features

Disclaimer about async operations

This library heavily depends on async operations using the CompletableFuture construct. Remember to handle them as your application will terminate without doing anything if the main thread is not executing any task. Please do not open redundant issues on GitHub because of this.

How to create a connection

Detailed Walkthrough

To create a new connection, start by creating a builder with the api you need:

If you want to use a custom serializer, specify it:

.serializer(new CustomControllerSerializer())

Now select the type of connection that you need:

You can now customize the API with these options:

There are also platform specific options:

  1. Web
    • historyLength: The amount of messages to sync from the companion device
      .historyLength(WebHistoryLength.THREE_MONTHS)
      
  2. Mobile
    • device: the device you want to fake:
      .device(CompanionDevice.android(false)) // Standard Android
      .device(CompanionDevice.android(true)) //Business android
      .device(CompanionDevice.ios(false)) // Standard iOS
      .device(CompanionDevice.ios(true)) // Business iOS
      .device(CompanionDevice.kaiOs()) // Standard KaiOS
      
    • businessCategory: the category of your business account
      .businessCategory(new BusinessCategory(id, name))
      
    • businessEmail: the email of your business account
      .businessEmail("email@domanin.com")
      
    • businessWebsite: the website of your business account
      .businessWebsite("https://google.com")
      
    • businessDescription: the description of your business account
      .businessDescription("A nice description")
      
    • businessLatitude: the latitude of your business account
      .businessLatitude(37.386051)
      
    • businessLongitude: the longitude of your business account
      .businessLongitude(-122.083855)
      
    • businessAddress: the address of your business account
      .businessAddress("1600 Amphitheatre Pkwy, Mountain View")
      

IMPORTANT: All options are serialized: there is no need to specify them again when deserializing an existing session

Finally select the registration status of your session:

Now you can connect to your session:

.connect()

to connect to Whatsapp. Remember to handle the result using, for example, join to await the connection's result. Finally, if you want to pause the current thread until the connection is closed, use:

.awaitDisconnection()
Web QR Pairing Example
Whatsapp.webBuilder() // Use the Web api
      .newConnection() // Create a new connection
      .unregistered(QrHandler.toTerminal()) // Print the QR to the terminal
      .addLoggedInListener(api -> System.out.printf("Connected: %s%n", api.store().privacySettings())) // Print a message when connected
      .addDisconnectedListener(reason -> System.out.printf("Disconnected: %s%n", reason)) // Print a message when disconnected
      .addNewChatMessageListener(message -> System.out.printf("New message: %s%n", message.toJson())) // Print a message when a new chat message arrives
      .connect() // Connect to Whatsapp asynchronously
      .join() // Await the result
      .awaitDisconnection(); // Wait 
Web Pairing Code Example
System.out.println("Enter the phone number(include the country code prefix, but no +, spaces or parenthesis):")
var scanner = new Scanner(System.in);
var phoneNumber = scanner.nextLong();
Whatsapp.webBuilder() // Use the Web api
      .newConnection() // Create a new connection
      .unregistered(phoneNumber, PairingCodeHandler.toTerminal()) // Print the pairing code to the terminal
      .addLoggedInListener(api -> System.out.printf("Connected: %s%n", api.store().privacySettings())) // Print a message when connected
      .addDisconnectedListener(reason -> System.out.printf("Disconnected: %s%n", reason)) // Print a message when disconnected
      .addNewChatMessageListener(message -> System.out.printf("New message: %s%n", message.toJson())) // Print a message when a new chat message arrives
      .connect() // Connect to Whatsapp asynchronously
      .join() // Await the result
      .awaitDisconnection(); // Wait 
Mobile Example
System.out.println("Enter the phone number(include the country code prefix, but no +, spaces or parenthesis):")
var scanner = new Scanner(System.in);
var phoneNumber = scanner.nextLong();
Whatsapp.mobileBuilder() // Use the Mobile api
      .newConnection() // Create a new connection
      .device(CompanionDevice.ios(false)) // Use a non-business iOS account
      .unregistered() // If the connection was just created, it needs to be registered
      .verificationCodeMethod(VerificationCodeMethod.SMS) // If the connection was just created, send an SMS OTP
      .verificationCodeSupplier(() -> { // Called when the OTP needs to be sent to Whatsapp
      System.out.println("Enter OTP: ");
var scanner = new Scanner(System.in);
          return scanner.nextLine();
      })
              .register(phoneNumber) // Register the phone number asynchronously, if necessary
      .join() // Await the result
      .whatsapp() // Access the Whatsapp instance
      .addLoggedInListener(api -> System.out.printf("Connected: %s%n", api.store().privacySettings())) // Print a message when connected
      .addDisconnectedListener(reason -> System.out.printf("Disconnected: %s%n", reason)) // Print a message when disconnected
      .addNewChatMessageListener(message -> System.out.printf("New message: %s%n", message.toJson())) // Print a message when a new chat message arrives
      .connect() // Connect to Whatsapp asynchronously
      .join() // Await the result
      .awaitDisconnection(); // Wait 

How to close a connection

There are three ways to close a connection:

  1. Disconnect

    api.disconnect();
    

    IMPORTANT: The session remains valid for future uses

  2. Reconnect

    api.reconnect();
    

    IMPORTANT: The session remains valid for future uses

  3. Log out

    api.logout();
    

    IMPORTANT: The session doesn't remain valid for future uses

What is a listener and how to register it

Listeners are crucial to handle events related to Whatsapp and implement logic for your application. Listeners can be used either as:

  1. Standalone concrete implementation

    If your application is complex enough, it's preferable to divide your listeners' logic across multiple specialized classes. To create a new concrete listener, declare a class or record that implements the Listener interface:

    import it.auties.whatsapp.api.Listener;
    
    public class MyListener implements Listener {
     @Override
     public void onLoggedIn() {
         System.out.println("Hello :)");
     }
    }
    

    Remember to register this listener:

    api.addListener(new MyListener());
    
  2. Functional interface

    If your application is very simple or only requires this library in small operations, it's preferable to add a listener using a lambda instead of using full-fledged classes. To declare a new functional listener, call the method add followed by the name of the listener that you want to implement without the on suffix:

    api.addDisconnectedListener(reason -> System.out.println("Goodbye: " + reason));
    

    All lambda listeners can access the instance of Whatsapp that called them:

    api.addDisconnectedListener((whatsapp, reason) -> System.out.println("Goodbye: " + reason));
    

    This is extremely useful if you want to implement a functionality for your application in a compact manner:

     Whatsapp.newConnection()
                 .addLoggedInListener(() -> System.out.println("Connected"))
                 .addNewMessageListener((whatsapp, info) -> whatsapp.sendMessage(info.chatJid(), "Automatic answer", info))
                 .connect()
                 .join();
    

How to handle serialization

In the original version of WhatsappWeb, chats, contacts and messages could be queried at any from Whatsapp's servers. The multi-device implementation, instead, sends all of this information progressively when the connection is initialized for the first time and doesn't allow any subsequent queries to access the latter. In practice, this means that this data needs to be serialized somewhere. The same is true for the mobile api.

By default, this library serializes data regarding a session at $HOME/.whatsapp4j/[web|mobile]/<session_id>. The data is stored in protobuf files.

If your application needs to serialize data in a different way, for example in a database create a custom implementation of ControllerSerializer. Then make sure to specify your implementation in the Whatsapp builder. This is explained in the "How to create a connection" section.

How to handle session disconnects

When the session is closed, the onDisconnect method in any listener is invoked. These are the three reasons that can cause a disconnect:

  1. DISCONNECTED

    A normal disconnection. This doesn't indicate any error being thrown.

  2. RECONNECTING

    The client is being disconnected but only to reopen the connection. This always happens when the QR is first scanned for example.

  3. LOGGED_OUT

    The client was logged out by itself or by its companion. By default, no error is thrown if this happens, though this behaviour can be changed easily:

    import it.auties.whatsapp.api.DisconnectReason;
    import it.auties.whatsapp.api.Listener;import it.auties.whatsapp.api.WhatsappListener;
    
    class ThrowOnLogOut implements Listener {
        @Override
        public void onDisconnected(DisconnectReason reason) {
            if (reason != SocketEvent.LOGGED_OUT) {
                return;
            }
    
            throw new RuntimeException("Hey, I was logged off :/");
        }
    }
    
  4. BANNED

    The client was banned by Whatsapp, usually happens when sending spam messages to people that aren't in your contact list

How to query chats, contacts, messages and status

Access the store associated with a connection by calling the store method:

var store = api.store();

IMPORTANT: When your program first starts up, these fields will be empty. For each type of data, an event is fired and listenable using a WhatsappListener

You can access all the chats that are in memory:

var chats = store.chats();

Or the contacts:

var contacts = store.contacts();

Or even the status:

var status = store.status();

Data can also be easily queried by using these methods:

How to query other data

To access information about the companion device:

var companion = store.jid();

This object is a jid like any other, but it has the device field filled to distinguish it from the main one. Instead, if you only need the phone number:

var phoneNumber = store.jid().toPhoneNumber();

All the settings and metadata about the companion is available inside the Store class

var store = api.store();

Explore of the available methods!

How to query cryptographic data

Access keys store associated with a connection by calling the keys method:

var keys = api.keys();

There are several methods to access and query cryptographic data, but as it's only necessary for advanced users, please check the javadocs if this is what you need.

How to send messages

To send a message, start by finding the chat where the message should be sent. Here is an example:

var chat = api.store()
        .findChatByName("My Awesome Friend")
        .orElseThrow(() -> new NoSuchElementException("Hey, you don't exist"));

All types of messages supported by Whatsapp are supported by this library:

IMPORTANT: Buttons are not documented here because they are unstable. If you are interested you can try to use them, but they are not guaranteed to work. There are some examples in the tests directory.

How to wait for replies

If you want to wait for a single reply, use:

var response = api.awaitReply(info).join(); 

You can also register a listener, but in many cases the async/await paradigm is easier to use then callback based listeners.

How to delete messages

var result = api.delete(someMessage, everyone); // Deletes a message for yourself or everyone

How to change your status

To change the status of the client:

api.changePresence(true); // online
api.changePresence(false); // offline

If you want to change the status of your companion, start by choosing the right presence: These are the allowed values:

Then, execute this method:

api.changePresence(chat,  presence);

IMPORTANT: The changePresence method returns a CompletableFuture: remember to handle this async construct if needed

How to query the last known presence for a contact

To query the last known status of a Contact, use the following snippet:

var lastKnownPresenceOptional = contact.lastKnownPresence();

If the returned value is an empty Optional, the last status of the contact is unknown.

Whatsapp starts sending updates regarding the presence of a contact only when:

To force Whatsapp to send these updates use:

api.subscribeToPresence(contact);

Then, after the subscribeToUserPresence's future is completed, query again the presence of that contact.

Query data about a group, or a contact

About
var status = api.queryAbout(contact) // A completable future
      .join() // Wait for the future to complete
      .flatMap(ContactAboutResponse::about) // Map the response to its status
      .orElse(null); // If no status is available yield null
Profile picture or chat picture
var picture = api.queryPicture(contact) // A completable future
      .join() // Wait for the future to complete
      .orElse(null); // If no picture is available yield null
Group's Metadata
var metadata = api.queryGroupMetadata(group); // A completable future
      .join(); // Wait for the future to complete

Search messages

var messages = chat.messages(); // All the messages in a chat
var firstMessage = chat.firstMessage(); // First message in a chat chronologically
var lastMessage = chat.lastMessage(); // Last message in a chat chronologically 
var starredMessages = chat.starredMessages(); // All the starred messages in a chat

Change the state of a chat

Mute a chat
var future = api.muteChat(chat);
Unmute a chat
var future = api.unmuteChat(chat);
Archive a chat
var future = api.archiveChat(chat);
Unarchive a chat
var future = api.unarchiveChat(chat);
Change ephemeral message status in a chat
var future = api.changeEphemeralTimer(chat,  ChatEphemeralTimer.ONE_WEEK);
Mark a chat as read
var future = api.markChatRead(chat);
Mark a chat as unread
var future = api.markChatUnread(chat);
Pin a chat
var future = api.pinChat(chat);
Unpin a chat
var future = api.unpinChat(chat);
Clear a chat
var future = api.clearChat(chat, false);
Delete a chat
var future = api.deleteChat(chat);

Change the state of a participant of a group

Add a contact to a group
var future = api.addGroupParticipant(group, contact);
Remove a contact from a group
var future = api.removeGroupParticipant(group, contact);
Promote a contact to admin in a group
var future = api.promoteGroupParticipant(group, contact);
Demote a contact to user in a group
var future = api.demoteGroupParticipant(group, contact);

Change the metadata or settings of a group

Change group's name/subject
var future = api.changeGroupSubject(group, newName);
Change or remove group's description
var future = api.changeGroupDescription(group, newDescription);
Change a setting in a group
var future = api.changeGroupSetting(group, GroupSetting.EDIT_GROUP_INFO, GroupPolicy.ANYONE);
Change or remove the picture of a group
var future = api.changeGroupPicture(group, img);

Other group related methods

Create a group
var future = api.createGroup("A nice name :)", friend, friend2);
Leave a group
var future = api.leaveGroup(group);
Query a group's invite code
var future = api.queryGroupInviteCode(group);
Revoke a group's invite code
var future = api.revokeGroupInvite(group);
Accept a group invite
var future = api.acceptGroupInvite(inviteCode);

2FA (Mobile api only)

Enable 2FA
var future = api.enable2fa("000000", "mail@domain.com");
Disable 2FA
var future = api.disable2fa();

Calls (Mobile api only)

Start a call
var future = api.startCall(contact);

IMPORTANT: Currently there is no audio/video support

Stop or reject a call
var future = api.stopCall(contact);

Communities

Newsletters / Channels

Some methods may not be listed here, all contributions are welcomed to this documentation!

Some methods may not be supported on the mobile api, please report them, so I can fix them.

Ideally I'd like all of them to work.