TMS FNC Cloud WhatsApp Guides
TTMSFNCCloudWhatsApp sends and receives messages through the WhatsApp
Business Cloud API. Every send goes through the same shape: a
TTMSFNCCloudWhatsAppMessage with a SendTo number, a MessageType, and
the payload object that matches that type — Text, Media, Location,
Contacts or Template. Convenience methods (SendTextMessage,
SendImage, SendDocument, SendLocation, SendContacts, SendTemplate)
build that message for you; SendMessage takes one you built yourself when
you need full control. This page covers authentication, sending a message,
template messages, and combining the two.
Authentication
Set Authentication.Key to the permanent access token of your Meta app, and
PhoneNumberID to the ID of the business phone number that sends the
messages — both come from the WhatsApp Business account in the Meta developer
console. Every request carries them, so set both before the first send.
Outcomes are reported through OnSendSuccess (the sent message, with the
service-assigned ID filled in) and OnError (the provider's error text);
media uploads report through OnUploadSuccess and downloads through
OnDownloadFileSuccess.
Sending a message
SendWhatsAppMessage is an alias for SendMessage — the same method
under a second, unambiguous name, which matters because forms and data
modules frequently already have their own SendMessage. Either name accepts
a message you built yourself, which is the route to take when you need a
property the convenience methods do not expose, such as ReplyToID for
threading a reply onto an inbound message.
procedure TForm1.SendGreeting(const ANumber: string);
var
Msg: TTMSFNCCloudWhatsAppMessage;
begin
TMSFNCCloudWhatsApp1.Authentication.Key := 'your-permanent-access-token';
TMSFNCCloudWhatsApp1.PhoneNumberID := 'your-phone-number-id';
TMSFNCCloudWhatsApp1.OnSendSuccess := WhatsAppSent;
TMSFNCCloudWhatsApp1.OnError := WhatsAppError;
Msg := TTMSFNCCloudWhatsAppMessage.Create;
Msg.SendTo := ANumber; // Recipient in international format.
Msg.MessageType := mtText;
Msg.Text.Body := 'Your order has shipped.';
Msg.Text.PreviewURL := False; // True renders a link preview.
// SendWhatsAppMessage is an alias for SendMessage - identical behaviour,
// but an unambiguous name in a form that also has its own SendMessage.
// Do NOT free Msg: the component takes ownership of it from here on.
TMSFNCCloudWhatsApp1.SendWhatsAppMessage(Msg);
end;
procedure TForm1.WhatsAppSent(Sender: TObject; AMessage: TTMSFNCCloudWhatsAppMessage);
begin
// ID is filled in from the service response and the message is kept in the
// component's Messages collection, so it can be replied to later.
ShowMessage('Sent, message id ' + AMessage.ID);
end;
procedure TForm1.WhatsAppError(Sender: TObject; AErrorMessage: string);
begin
ShowMessage('WhatsApp error: ' + AErrorMessage);
end;
Important
The component takes ownership of a message passed to SendMessage or
SendWhatsAppMessage: on success it is added to the Messages collection
(which frees its items), and on failure it is freed immediately. Never free
a message you handed to either method, and do not keep the reference after
the call.
Template messages
Outside a 24-hour customer service window, WhatsApp only accepts a template message — a layout pre-registered and approved in your WhatsApp Business account, with placeholders your code fills in. This is the mechanism to use for order confirmations, appointment reminders and one-time codes.
Three pieces make one up:
| Type | Purpose |
|---|---|
TTMSFNCCloudWhatsAppTemplate |
Name and LanguageCode identify the approved template; Components holds its parts |
TTMSFNCCloudWhatsAppTemplateComponent |
One part of the template — ComponentType is tctHeader, tctBody or tctButton; a button also needs its zero-based ButtonIndex |
TTMSFNCCloudWhatsAppTemplateParameter |
One placeholder value, added through the Parameters helpers |
Add parameters in the order the template's placeholders ({{1}}, {{2}}, …)
appear, using the helper that matches the type the template expects:
| Helper | Parameter type | Use for |
|---|---|---|
AddText |
tptText |
a plain text substitution |
AddMedia |
tptImage / tptDocument / tptVideo |
a header image, document or video, by media ID or public link |
AddCurrency |
tptCurrency |
an amount — the code, the value in thousandths (49900 for 49.90), and a fallback string |
AddDateTime |
tptDateTime |
a date, with a fallback string |
AddPayload |
tptPayload |
the payload a quick-reply button posts back |
SendTemplate is the short route: it builds the message, sets
MessageType to mtTemplate, copies your template into it and sends it.
Because it copies, the template you pass stays yours to free.
procedure TForm1.SendOrderConfirmation(const ANumber, AOrderID: string);
var
Template: TTMSFNCCloudWhatsAppTemplate;
Header, Body, Button: TTMSFNCCloudWhatsAppTemplateComponent;
begin
TMSFNCCloudWhatsApp1.OnSendSuccess := WhatsAppSent;
TMSFNCCloudWhatsApp1.OnError := WhatsAppError;
Template := TTMSFNCCloudWhatsAppTemplate.Create;
try
// Name and LanguageCode must match a template approved in the WhatsApp
// Business account; the placeholders are filled in below, in order.
Template.Name := 'order_confirmation';
Template.LanguageCode := 'en_US';
// A header component carries at most one parameter, often an image.
Header := Template.Components.AddComponent(tctHeader);
Header.Parameters.AddMedia(tptImage, '', 'https://example.com/logo.png');
// Body parameters are substituted for {{1}}, {{2}}, ... in order.
Body := Template.Components.AddComponent(tctBody);
Body.Parameters.AddText(AOrderID);
Body.Parameters.AddCurrency('EUR', 49900, 'EUR 49.90');
Body.Parameters.AddDateTime('March 3, 2026');
// A button component needs the zero-based index of the button it fills.
Button := Template.Components.AddComponent(tctButton, 0);
Button.Parameters.AddPayload('TRACK_' + AOrderID);
// SendTemplate builds the mtTemplate message itself and copies the
// template into it, so the local Template stays yours to free.
TMSFNCCloudWhatsApp1.SendTemplate(ANumber, Template);
finally
Template.Free;
end;
end;
Combining a template with a threaded follow-up
SendTemplate cannot set every message property, so when you need one it
does not cover — ReplyToID, for instance — set MessageType := mtTemplate
on a message you build yourself and fill its own Template object. Delivering
a template also opens the conversation window, so a free-form text message
chained from OnSendSuccess is then accepted:
{ FTemplateReplyNumber is a private string field on the form, carrying the
recipient from the template send to the follow-up. }
procedure TForm1.ReplyWithTemplate(const ANumber, AInboundMessageID: string);
var
Msg: TTMSFNCCloudWhatsAppMessage;
begin
TMSFNCCloudWhatsApp1.OnSendSuccess := TemplateReplySent;
TMSFNCCloudWhatsApp1.OnError := WhatsAppError;
// SendTemplate cannot set ReplyToID, so build the message by hand: set
// MessageType to mtTemplate and fill the message's own Template object.
Msg := TTMSFNCCloudWhatsAppMessage.Create;
Msg.SendTo := ANumber;
Msg.MessageType := mtTemplate;
Msg.ReplyToID := AInboundMessageID;
Msg.Template.Name := 'ticket_received';
Msg.Template.LanguageCode := 'en_US';
Msg.Template.Components.AddComponent(tctBody).Parameters.AddText('24 hours');
FTemplateReplyNumber := ANumber;
TMSFNCCloudWhatsApp1.SendWhatsAppMessage(Msg);
end;
procedure TForm1.TemplateReplySent(Sender: TObject; AMessage: TTMSFNCCloudWhatsAppMessage);
var
FollowUp: TTMSFNCCloudWhatsAppMessage;
begin
// Only chain after the template landed, and only once - clear the handler
// so the follow-up's own success does not start another round.
if AMessage.MessageType <> mtTemplate then
Exit;
TMSFNCCloudWhatsApp1.OnSendSuccess := WhatsAppSent;
// The template opened the conversation window, so a free-form text message
// is now allowed - sent as a reply to the template we just delivered.
FollowUp := TTMSFNCCloudWhatsAppMessage.Create;
FollowUp.SendTo := FTemplateReplyNumber;
FollowUp.MessageType := mtText;
FollowUp.ReplyToID := AMessage.ID;
FollowUp.Text.Body := 'Reply to this message to add details to your ticket.';
TMSFNCCloudWhatsApp1.SendWhatsAppMessage(FollowUp);
end;
Common mistakes
- Freeing a sent message. The component owns it after
SendMessage/SendWhatsAppMessage. A template passed toSendTemplateis the opposite — that one is copied, so you must free it. - Sending free-form text outside the 24-hour window. It is rejected; send an approved template first.
- Mismatched template parameters. The name, language code, component set
and parameter order and types must match the approved template exactly, or
the service rejects the message — the failure arrives in
OnError, not as an exception. - Setting a payload amount in the wrong unit.
AddCurrencytakes thousandths of the currency unit, not the amount itself. - Forgetting
PhoneNumberID. Without it the request path is incomplete and every send fails, even with a valid token. - Re-entering
OnSendSuccess. Sending from that handler makes it fire again for the new message; guard onMessageTypeorID, or swap the handler, to avoid a loop.
See also
- TTMSFNCCloudWhatsApp — full class reference
- Get started
- Release notes