Skip to main content
Table of Contents

Receiving messages

TTMSFNCWhatsAppReceiver brings the messages people send to your WhatsApp Business number into a Delphi application. It is a secure WebSocket client that connects to a TTMSFNCWhatsAppServer — the host of your webhook — receives the payloads the server forwards, and parses each incoming WhatsApp message into a TTMSFNCWhatsAppReceiverMessage object with the sender, the message type, and the text, media, location, or contact details. Use it for a support desk that shows incoming chats, for order intake over WhatsApp, or for any workflow that should react to a customer's message. It runs in VCL and FMX applications and in TMS WEB Core browser applications. This chapter covers connecting to the server, how messages flow through the receiver's events, reading each message type, and handling messages safely.

Connecting to the server

Set HostName, Port, and — when the server uses one — PathName to the address of the TTMSFNCWhatsAppServer, then set Active to True or call Connect. Because WhatsApp requires the server to run over TLS, the receiver always connects securely; UseSSL is enabled and cannot be changed. In VCL and FMX applications the OpenSSL libraries must therefore be deployed with the application (see Secure connections).

Connect to the WhatsApp serverPascal
procedure TForm1.ConnectBtnClick(Sender: TObject);
begin
  // The receiver always connects over TLS (wss://).
  TMSFNCWhatsAppReceiver1.HostName := 'hooks.example.com';
  TMSFNCWhatsAppReceiver1.Port := 443;
  TMSFNCWhatsAppReceiver1.PathName := 'whatsapp';   // the server's PathName, if any
  TMSFNCWhatsAppReceiver1.Connect;
end;

procedure TForm1.TMSFNCWhatsAppReceiver1Connect(Sender: TObject;
  AConnection: TTMSFNCWebSocketConnection);
begin
  StatusLabel.Text := 'Receiving WhatsApp messages';
end;

procedure TForm1.TMSFNCWhatsAppReceiver1Disconnect(Sender: TObject;
  AConnection: TTMSFNCWebSocketConnection);
begin
  StatusLabel.Text := 'Disconnected';
end;

How incoming messages flow

Every message the server sends raises the generic event first, then the WhatsApp-specific one:

  1. OnMessageReceived fires for every text message, or OnBinaryDataReceived for every binary message. This includes both the webhook payloads the server forwards and messages the server sends itself with BroadcastMessage or SendMessageTo.
  2. A text message is then parsed. When it is a WhatsApp payload of the messages field that contains an incoming message, OnWhatsAppMessageReceived fires with the parsed message in AMessage.

Anything else — status updates, payloads of other webhook fields, or your own server messages — raises only the generic event and is otherwise discarded.

Reading the message

TTMSFNCWhatsAppReceiverMessage describes one incoming message. The common fields are always filled:

Field Contents
From.Name The sender's WhatsApp profile name.
From.Phone The sender's phone number.
From.WhatsAppID The sender's WhatsApp ID.
ID The message ID assigned by WhatsApp.
TimeStamp The time the message was sent, as the Unix timestamp string WhatsApp delivers.
IsReply True when the message replies to an earlier message.
MessageType Which of the detail objects below carries the content.

MessageType selects the detail object to read:

MessageType Details
wamtText Text.Body
wamtImage, wamtVideo Media.ID, Media.MimeType, Media.Checksum, Media.Caption
wamtDocument Media.ID, Media.Filename, Media.MimeType, Media.Checksum
wamtAudio, wamtSticker Media.ID, Media.MimeType, Media.Checksum
wamtLocation Location.Latitude, Location.Longitude, Location.LocationName, Location.Address
wamtContacts Contacts, a list of contact cards

Media messages carry a media ID, not the file itself. Download the file through the WhatsApp Cloud API media endpoint with that ID and your access token; the SHA-256 value in Checksum lets you verify the download.

Handle each message typePascal
function TForm1.DescribeMessage(AMessage: TTMSFNCWhatsAppReceiverMessage): string;
begin
  case AMessage.MessageType of
    wamtText:
      Result := AMessage.Text.Body;
    wamtImage, wamtVideo:
      Result := Format('%s (%s) caption: %s',
        [AMessage.Media.ID, AMessage.Media.MimeType, AMessage.Media.Caption]);
    wamtDocument:
      Result := Format('Document %s, media ID %s', [AMessage.Media.Filename, AMessage.Media.ID]);
    wamtAudio, wamtSticker:
      Result := Format('Media ID %s (%s)', [AMessage.Media.ID, AMessage.Media.MimeType]);
    wamtLocation:
      Result := Format('%s, %s at %.5f, %.5f',
        [AMessage.Location.LocationName, AMessage.Location.Address,
         AMessage.Location.Latitude, AMessage.Location.Longitude]);
    wamtContacts:
      Result := Format('%d shared contact(s)', [AMessage.Contacts.Count]);
  end;

  if AMessage.IsReply then
    Result := '[reply] ' + Result;
end;

Reading shared contacts

A contacts message holds one or more TTMSFNCWhatsAppReceiverContact cards. Each card has a ContactName (formatted, first, middle, and last name, prefix, and suffix), a Birthday, an Organization (company, department, and title), and lists of Phones, Emails, Addresses, and URLs. Phone entries carry a PhoneType and, when the number uses WhatsApp, a WhatsAppID; addresses, e-mail addresses, and URLs carry a home or work type.

Read shared contact cardsPascal
procedure TForm1.ListContacts(AMessage: TTMSFNCWhatsAppReceiverMessage; ALines: TStrings);
var
  Contact: TTMSFNCWhatsAppReceiverContact;
  Phone: TTMSFNCWhatsAppReceiverContactPhone;
  Email: TTMSFNCWhatsAppReceiverContactEmail;
begin
  if AMessage.MessageType <> wamtContacts then
    Exit;

  for Contact in AMessage.Contacts do
  begin
    ALines.Add(Contact.ContactName.FormattedName);
    if Contact.Organization.Company <> '' then
      ALines.Add('  ' + Contact.Organization.Title + ', ' + Contact.Organization.Company);
    for Phone in Contact.Phones do
      // WhatsAppID is set when the number has a WhatsApp account.
      ALines.Add('  Phone: ' + Phone.Phone);
    for Email in Contact.Emails do
      ALines.Add('  E-mail: ' + Email.Email);
  end;
end;

Handling messages safely

Two rules apply to OnWhatsAppMessageReceived in VCL and FMX applications:

  • The event runs on a background thread. Parsing happens on the thread that reads the connection, so queue every user interface update with TThread.Queue or TThread.Synchronize. The generic OnMessageReceived, OnConnect, and OnDisconnect events follow the client's AutoSyncEvents setting.
  • AMessage is freed when the handler returns. Read the fields you need inside the handler, or keep a copy: create a TTMSFNCWhatsAppReceiverMessage and call Assign. ToJSON turns a message into JSON for storage.

Combining subscriptions, the message model, and safe handling

The example announces the business number the receiver is interested in as soon as it connects (a protocol handled by the server in Forwarding messages), copies every parsed message, and displays and stores the copy on the main thread.

Subscribe, copy, and display messages safelyPascal
procedure TForm1.TMSFNCWhatsAppReceiver1Connect(Sender: TObject;
  AConnection: TTMSFNCWebSocketConnection);
begin
  // A server-defined protocol message; see the server's forwarding guide.
  TMSFNCWhatsAppReceiver1.Send('subscribe:' + FPhoneNumberID);
end;

procedure TForm1.TMSFNCWhatsAppReceiver1WhatsAppMessageReceived(Sender: TObject;
  AMessage: TTMSFNCWhatsAppReceiverMessage);
var
  Kept: TTMSFNCWhatsAppReceiverMessage;
begin
  // AMessage is freed after this handler: keep a copy of the whole message.
  Kept := TTMSFNCWhatsAppReceiverMessage.Create;
  Kept.Assign(AMessage);

  // This event runs on a background thread in VCL and FMX applications.
  TThread.Queue(nil,
    procedure
    begin
      try
        Memo1.Lines.Add(Kept.From.Name + ': ' + DescribeMessage(Kept));
        FInbox.Add(Kept.ID, Kept.ToJSON);   // store the message as JSON
      finally
        Kept.Free;
      end;
    end);
end;

procedure TForm1.TMSFNCWhatsAppReceiver1MessageReceived(Sender: TObject;
  AConnection: TTMSFNCWebSocketConnection; const AMessage: string);
begin
  // Raised for every text message from the server, including the WhatsApp
  // payloads above and messages the server sends itself.
  if not AMessage.StartsWith('{') then
    StatusLabel.Text := AMessage;
end;

Browser applications

The receiver is the one component of this product available in TMS WEB Core, through the WEBLib.TMSFNCWhatsAppReceiver unit. It uses the browser's WebSocket support, and the browser performs TLS, so no OpenSSL deployment is needed. The differences from VCL and FMX:

Area VCL and FMX TMS WEB Core
Lost connection OnDisconnect does not fire when the connection drops abruptly; ping the server to detect it. OnDisconnect fires whenever the connection is lost.
Ping, OnPing, OnPong Available (public). Not available.
Disconnect(False) Aborts without a close frame. Not available; Disconnect always closes normally.
AutoSyncEvents, ConnectTimeout Available (public). Not available.
Event thread OnWhatsAppMessageReceived runs on a background thread. All events run on the browser's single thread.

Pitfalls

  • Connect to the WhatsApp server, not to Meta. The receiver only talks to a TTMSFNCWhatsAppServer; WhatsApp itself never opens WebSocket connections.
  • Deploy OpenSSL on the desktop. The connection is always secure, so VCL and FMX applications fail to connect without the OpenSSL libraries.
  • Copy before you queue. A queued procedure that reads AMessage after the handler returned reads a freed object.
  • Not every payload becomes a message. Delivery and read receipts arrive through OnMessageReceived only.
  • Detect lost connections on the desktop. Use Ping and OnPong, as described in Connection health.

See also