Elegant WhatsApp integration with .NET Core

It's quite annoying that WhatsApp, one of the most popular messaging platforms, does not (yet) provide a consumer API. Although they might have valid reasons for not exposing some kind of public API, it forces the invention of other creative ways to automate WhatsApp messages.

Disclaimer: By posting this blog post, I am not endorsing any activities that are in violation of the WhatsApp's terms of use. The source code shared in this article is intended for experimentation purposes only and is to be used at one's own risk.

After searching the web for ways to integrate third-party apps with WhatsApp, I stumbled upon a clever technique that uses test automation tools to trick WhatsApp Web into thinking that it's interacting with a real person.

The basic idea is to have a browser, like Chrome, be controlled by a browser driver, like the Selenium Chrome Driver, which operates on programming instructions for interacting with websites, like Whatsapp Web. There are many examples on the interwebs of using this technique in other technologies, like Node, but I wanted to bring similar functionality to .NET Core in a simple and elegant way.

So coding away one evening, I ended up with a component with the following usage as the end result:

public void ConfigureServices(IServiceCollection services)  
{
    //...
    services.AddWhatsApp<AwesomeWhatsAppHandler>();
}
For the impatient, you can find the source code here

After adding this special WhatsApp component to the IServicesCollection, when the application runs, the integration will just work. But how?

It's quite simple, really. When the application starts up, it starts an instance of Whatsapp Web in the background and proxies through the QR code image from the login prompt. It is completely up to the application on how- and where to rely the QR code image for authentication, but keep in mind that it's constantly changing, so the application needs to be able to handle that.

When the user ultimately scans the QR code image with the WhatsApp mobile app, it logs into the WhatsApp Web session and starts to listen for new incoming messages. Likewise, in the event of a new message, the application is served an event to act upon.

As seen below, looking at the omitted source of the AddWhatsApp extension method, this solution comprises out of three components namely, a background service, a connector and an event executor.

public static IServiceCollection AddWhatsApp<T>(this IServiceCollection services, Action<WhatsAppOptions> options = null)  
            where T: IWhatsAppEventExecutor
{
    //...
    services.TryAddSingleton<IHostedService, WhatsAppHostedService>();
    services.TryAddSingleton<IWhatsAppConnector, WhatsAppConnector>();
    services.TryAddSingleton(typeof(IWhatsAppEventExecutor), typeof(T));

    return services;
}

The WhatsAppHostedService is an implementation of an IHostedService, which is the standard mechanism for running background logic in ASP.NET Core.

The WhatsAppConnector is the main WhatsApp driver and is initiated by the hosted service at startup. This connector implements the IWhatsAppConnector interface and is responsible for sending messages via WhatsApp, as well as starting and stopping the session:

public interface IWhatsAppConnector  
{
    Task SendMessageAsync(string number, string message);
    Task StartAsync();
    Task StopAsync();
}

The authentication and incoming messaging logic happen behind the scenes inside the implementation of the StartAsync method:

public async Task StartAsync()  
{
    //...

    driver = new ChromeDriver(driverLocation, chromeOption);
    driver.Navigate().GoToUrl(options.WhatsAppUrl);

    //...

    OnStarted();

    while (true)
    {
        await Authenticate();
        OnAuthenticated();
        await WaitForMessages(options.CheckForNewMessagesInterval);
    }
}

During a WhatsApp session, certain workflow events are published that can be consumed by the application using it, and this is where the IWhatsAppEventExecutor comes in:

public interface IWhatsAppEventExecutor  
{
    Task OnScreenshotReceivedAsync(byte[] uiImage);
    Task OnMessagesReceived(UnreadMessage[] messages);
    Task OnStartedAsync();
    Task OnStoppedAsync();
    Task OnAuthenticatingAsync(string qrCodeBase64String);
    Task OnAuthenticatedAsync();
}

By implementing the interface above, the application can integrate with WhatsApp Web to receive the authentication QR code image as it changes and, of course, any new incoming messages.

For this proof of concept, the AwesomeWhatsAppHandler class implements IWhatsAppEventExecutor and uses SignalR to convey server-to-client push messages.

As a bonus extra, I've added a bit of flexibility to make the component configurable, by passing WhatsAppOptions to the AddWhatsApp method, either by code or by a config file.

In closing, I would like to stress that this code should be used with caution. If there is some kind of bug in the application, say for instance an endless loop somewhere, it may end up spamming your users and your number will be blocked by WhatsApp.

The source code is available on my repo

Happy Hacking!