Forwarding messages
Every event WhatsApp posts to the webhook passes through
TTMSFNCWhatsAppServer before it reaches your receivers. By default the server
forwards each payload unchanged to every connected
TTMSFNCWhatsAppReceiver, which suits a
single desktop application that shows incoming messages. As soon as there is
more than one receiver, more than one business number, or a need to store or
audit messages centrally, you want to look at each payload on the server first.
This chapter covers inspecting payloads in OnRawMessage, suppressing or
filtering the broadcast, routing payloads to selected receivers, and the other
traffic the server handles — messages from the receivers themselves and plain
HTTP requests.
Inspecting webhook payloads
OnRawMessage fires for
every POST that reaches the webhook, with the request body — WhatsApp's JSON
payload — in AMessage. Parse it there to log, store, or react to messages on
the server. The event runs on the server's connection thread, even when
AutoSyncEvents is enabled, so queue any user interface update with
TThread.Queue.
procedure TForm1.FormCreate(Sender: TObject);
begin
// Callback URL in the Meta dashboard: https://<your-domain>/
TMSFNCWhatsAppServer1.Port := 443;
TMSFNCWhatsAppServer1.VerifyToken := 'my-verify-token'; // same value as in the dashboard
TMSFNCWhatsAppServer1.CertificateFile := 'certs\cert.pem';
TMSFNCWhatsAppServer1.CertificateKeyFile := 'certs\key.pem';
TMSFNCWhatsAppServer1.RootCertificateFile := 'certs\chain.pem';
TMSFNCWhatsAppServer1.Active := True;
end;
procedure TForm1.TMSFNCWhatsAppServer1RawMessage(Sender: TObject;
AMessage: string; var ABroadcast: Boolean);
begin
// AMessage is the JSON body WhatsApp posted. This event runs on the
// server's connection thread, so queue user interface updates.
TThread.Queue(nil,
procedure
begin
Memo1.Lines.Add(AMessage);
end);
// Leave ABroadcast True to forward the payload to every connected receiver.
end;
Filtering what is forwarded
ABroadcast arrives as True; when it is still True after the handler
returns, the server broadcasts AMessage to all connected receivers with
BroadcastMessage.
Set it to False to keep a payload on the server. A common filter forwards only
payloads that carry incoming messages and drops delivery and read status
updates, which arrive on the same messages field.
uses
System.JSON;
procedure TForm1.TMSFNCWhatsAppServer1RawMessage(Sender: TObject;
AMessage: string; var ABroadcast: Boolean);
var
Json: TJSONValue;
Field: string;
begin
// Forward nothing unless the payload proves to be an incoming message.
ABroadcast := False;
Json := TJSONObject.ParseJSONValue(AMessage);
if not Assigned(Json) then
Exit;
try
// {"object":"whatsapp_business_account","entry":[{"changes":[{"field":"messages",
// "value":{"messages":[...]} or {"statuses":[...]}}]}]}
if Json.TryGetValue<string>('entry[0].changes[0].field', Field) and (Field = 'messages') and
Assigned(Json.FindValue('entry[0].changes[0].value.messages')) then
begin
ArchiveMessage(AMessage); // your own storage, safe to call from a thread
ABroadcast := True; // send it on to the connected receivers
end;
finally
Json.Free;
end;
end;
Routing to selected receivers
To send a payload to some receivers only, set ABroadcast to False and send
it yourself with
SendMessageTo, whose
selector picks the receivers. The inherited
BroadcastData and
SendDataTo send binary
data the same way, and BroadcastMessage can also push messages of your own —
for example a notice that the server restarts. Receivers get those messages
through their OnMessageReceived event; only WhatsApp-formatted JSON also
raises their OnWhatsAppMessageReceived.
Handling messages from receivers
Receivers can send messages to the server too, and the server raises the
inherited OnMessageReceived
and OnBinaryDataReceived
events for them. Use this for a small protocol of your own — a receiver that
subscribes to one business number, a receiver that acknowledges a message — and
store the result on the connection's
UserData (see the
server's Custom connection data
chapter). The connection events OnConnect, OnAllow, OnDisconnect,
OnClose, OnPing, and OnPong work as described in
Running a server.
Handling plain HTTP requests
Requests that are neither a WebSocket upgrade nor handled by you receive the
server's built-in HTTP response. Assign OnCommandGet to take over that
response for every plain HTTP request on the webhook path — for example to
serve a health check, or to answer 200 when the webhook uses a custom path
(see Webhook setup). The
webhook verification answer and payload forwarding still run after your
handler.
Combining subscriptions with routed forwarding
The example lets every receiver subscribe to one business phone number, remembers the subscription on its connection, and forwards each payload only to the receivers subscribed to the number that received it.
uses
System.JSON;
type
TSubscription = class
public
PhoneNumberID: string;
end;
procedure TForm1.TMSFNCWhatsAppServer1MessageReceived(Sender: TObject;
AConnection: TTMSFNCWebSocketConnection; const AMessage: string);
var
Conn: TTMSFNCWebSocketServerConnection;
begin
// A receiver sends: subscribe:<phone_number_id>
if AMessage.StartsWith('subscribe:') then
begin
Conn := AConnection as TTMSFNCWebSocketServerConnection;
if not Assigned(Conn.UserData) then
begin
Conn.OwnsUserData := True;
Conn.UserData := TSubscription.Create;
end;
TSubscription(Conn.UserData).PhoneNumberID := AMessage.Substring(Length('subscribe:'));
end;
end;
procedure TForm1.TMSFNCWhatsAppServer1RawMessage(Sender: TObject;
AMessage: string; var ABroadcast: Boolean);
var
Json: TJSONValue;
PhoneNumberID: string;
begin
// Do not broadcast to everyone; route by the business number that received it.
ABroadcast := False;
Json := TJSONObject.ParseJSONValue(AMessage);
if not Assigned(Json) then
Exit;
try
if not Json.TryGetValue<string>('entry[0].changes[0].value.metadata.phone_number_id',
PhoneNumberID) then
Exit;
finally
Json.Free;
end;
TMSFNCWhatsAppServer1.SendMessageTo(AMessage,
function(AConnection: TTMSFNCWebSocketServerConnection): Boolean
begin
Result := Assigned(AConnection.UserData) and
(TSubscription(AConnection.UserData).PhoneNumberID = PhoneNumberID);
end);
end;
Pitfalls
OnRawMessageruns on a background thread. Never touch controls from it directly, whateverAutoSyncEventsis set to.- Setting
ABroadcasttoFalsestops all forwarding. Send the payload yourself when some receivers should still get it. - Status updates share the
messagesfield. Filter on the presence ofmessagesin the payload when only incoming messages matter. - Answer quickly. Meta expects a prompt response to each
POST; move slow work such as database writes out ofOnRawMessagewhen it could delay the reply. - Receivers ignore other JSON. Messages you send yourself reach the
receivers'
OnMessageReceived, but do not raiseOnWhatsAppMessageReceivedunless they follow WhatsApp's payload format.
Related API
OnRawMessage,TTMSFNCWhatsAppServerMessageEventBroadcastMessage,BroadcastData,SendMessageTo,SendDataToOnMessageReceived,OnBinaryDataReceived,UserData
See also
- Webhook setup — register and verify the callback URL.
- Receiving messages — what the receivers do with forwarded payloads.
- Broadcasting messages — selectors and send methods in depth.